six-degrees 0.4.2 → 0.4.4

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 (282) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +3 -1
  3. package/.next/standalone/.next/build-manifest.json +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +41 -13
  5. package/.next/standalone/.next/routes-manifest.json +12 -0
  6. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
  12. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  15. package/.next/standalone/.next/server/app/_not-found.rsc +19 -17
  16. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +19 -17
  17. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +15 -13
  18. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  19. package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +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.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/scraper/route.js +1 -1
  38. package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/api/social/route/app-paths-manifest.json +3 -0
  44. package/.next/standalone/.next/server/app/api/social/route/build-manifest.json +12 -0
  45. package/.next/standalone/.next/server/app/api/social/route/server-reference-manifest.json +4 -0
  46. package/.next/standalone/.next/server/app/api/social/route.js +8 -0
  47. package/.next/standalone/.next/server/app/api/social/route.js.map +5 -0
  48. package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -0
  49. package/.next/standalone/.next/server/app/api/social/route_client-reference-manifest.js +3 -0
  50. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/import/page.js +1 -1
  56. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/import.html +1 -1
  59. package/.next/standalone/.next/server/app/import.rsc +23 -21
  60. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +23 -21
  61. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
  62. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +17 -15
  63. package/.next/standalone/.next/server/app/index.html +1 -1
  64. package/.next/standalone/.next/server/app/index.rsc +21 -19
  65. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +17 -15
  66. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +21 -19
  67. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  68. package/.next/standalone/.next/server/app/launch/page.js +1 -1
  69. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/app/launch.html +2 -2
  72. package/.next/standalone/.next/server/app/launch.rsc +23 -21
  73. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +23 -21
  74. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
  75. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +17 -15
  76. package/.next/standalone/.next/server/app/page.js +1 -1
  77. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  78. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  79. package/.next/standalone/.next/server/app/paths/page.js +1 -1
  80. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  81. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  82. package/.next/standalone/.next/server/app/paths.html +1 -1
  83. package/.next/standalone/.next/server/app/paths.rsc +23 -21
  84. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +23 -21
  85. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
  86. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +17 -15
  87. package/.next/standalone/.next/server/app/profile/page.js +1 -1
  88. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  89. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  90. package/.next/standalone/.next/server/app/profile.html +1 -1
  91. package/.next/standalone/.next/server/app/profile.rsc +23 -21
  92. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +23 -21
  93. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
  94. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +17 -15
  95. package/.next/standalone/.next/server/app/queue/page.js +1 -1
  96. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  97. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  98. package/.next/standalone/.next/server/app/queue.html +1 -1
  99. package/.next/standalone/.next/server/app/queue.rsc +23 -21
  100. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +23 -21
  101. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
  102. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +17 -15
  103. package/.next/standalone/.next/server/app/scores/page.js +1 -1
  104. package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
  105. package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
  106. package/.next/standalone/.next/server/app/scores.html +1 -1
  107. package/.next/standalone/.next/server/app/scores.rsc +23 -21
  108. package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +23 -21
  109. package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
  110. package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +17 -15
  111. package/.next/standalone/.next/server/app/settings/page.js +1 -1
  112. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  113. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  114. package/.next/standalone/.next/server/app/settings.html +1 -1
  115. package/.next/standalone/.next/server/app/settings.rsc +23 -21
  116. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +23 -21
  117. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  118. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +17 -15
  119. package/.next/standalone/.next/server/app/setup/page.js +1 -1
  120. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  121. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  122. package/.next/standalone/.next/server/app/setup.html +1 -1
  123. package/.next/standalone/.next/server/app/setup.rsc +23 -21
  124. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +23 -21
  125. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
  126. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +17 -15
  127. package/.next/standalone/.next/server/app/social/page/app-paths-manifest.json +3 -0
  128. package/.next/standalone/.next/server/app/social/page/build-manifest.json +20 -0
  129. package/.next/standalone/.next/server/app/social/page/next-font-manifest.json +11 -0
  130. package/.next/standalone/.next/server/app/social/page/react-loadable-manifest.json +1 -0
  131. package/.next/standalone/.next/server/app/social/page/server-reference-manifest.json +4 -0
  132. package/.next/standalone/.next/server/app/social/page.js +13 -0
  133. package/.next/standalone/.next/server/app/social/page.js.map +5 -0
  134. package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -0
  135. package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +3 -0
  136. package/.next/standalone/.next/server/app/social.html +1 -0
  137. package/.next/standalone/.next/server/app/social.meta +26 -0
  138. package/.next/standalone/.next/server/app/social.rsc +26 -0
  139. package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +26 -0
  140. package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +4 -0
  141. package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +30 -0
  142. package/.next/standalone/.next/server/app-paths-manifest.json +3 -1
  143. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +1 -1
  144. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17kn1mk._.js +8 -0
  145. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0uag-k5._.js → [root-of-the-server]__1dmc7-t._.js} +1 -1
  146. package/.next/standalone/.next/server/chunks/_1_fynia._.js +5 -5
  147. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
  148. package/.next/standalone/.next/server/chunks/_1xh07bp._.js +3 -0
  149. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_social_route_actions_1_3c6qn.js +3 -0
  150. package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +1 -1
  151. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16hs8kw._.js +3 -0
  152. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1feicqq._.js +3 -0
  153. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jeztd-._.js +3 -0
  154. package/.next/standalone/.next/server/chunks/ssr/_005q9n1._.js +3 -0
  155. package/.next/standalone/.next/server/chunks/ssr/_08_03wh._.js +3 -0
  156. package/.next/standalone/.next/server/chunks/ssr/_08cfj2b._.js +3 -0
  157. package/.next/standalone/.next/server/chunks/ssr/_0j4-lc3._.js +3 -0
  158. package/.next/standalone/.next/server/chunks/ssr/{_0hw_qnj._.js → _0wxbq78._.js} +1 -1
  159. package/.next/standalone/.next/server/chunks/ssr/_0xkur4p._.js +3 -0
  160. package/.next/standalone/.next/server/chunks/ssr/_15x6q32._.js +3 -0
  161. package/.next/standalone/.next/server/chunks/ssr/{_1cyxocj._.js → _1ap1tu_._.js} +2 -2
  162. package/.next/standalone/.next/server/chunks/ssr/_1mj739k._.js +3 -0
  163. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_social_page_actions_176i1kl.js +3 -0
  164. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +3 -3
  165. package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +1 -1
  166. package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +1 -1
  167. package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +3 -0
  168. package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +3 -0
  169. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  170. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  171. package/.next/standalone/.next/server/next-font-manifest.js +1 -1
  172. package/.next/standalone/.next/server/next-font-manifest.json +4 -0
  173. package/.next/standalone/.next/server/pages/404.html +1 -1
  174. package/.next/standalone/.next/server/pages/500.html +1 -1
  175. package/.next/standalone/.next/server/prefetch-hints.json +14 -0
  176. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  177. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  178. package/.next/standalone/.next/static/chunks/04a3zockahetx.js +1 -0
  179. package/.next/standalone/.next/static/chunks/0a5iadnmt9u9k.js +1 -0
  180. package/.next/standalone/.next/static/chunks/{2p6o90z0vgcwr.css → 0bo1dxn5gwzpk.css} +1 -1
  181. package/.next/standalone/.next/static/chunks/0g_p4p7oci18u.js +1 -0
  182. package/.next/standalone/.next/static/chunks/{0ax_9zzboyv6d.js → 17-rhp8sg4m1b.js} +2 -2
  183. package/.next/standalone/.next/static/chunks/1ra4cwcp75tfa.js +1 -0
  184. package/.next/standalone/.next/static/chunks/236f0jtp68j_h.js +1 -0
  185. package/.next/standalone/.next/static/chunks/24m745q_85u34.js +6 -0
  186. package/.next/standalone/.next/static/chunks/2_wcobfqry2uj.js +1 -0
  187. package/.next/standalone/.next/static/chunks/2dwtoszeq91us.js +1 -0
  188. package/.next/standalone/.next/static/chunks/2hvcevue9aro4.js +1 -0
  189. package/.next/standalone/.next/static/chunks/2uo9i03_05rqi.js +18 -0
  190. package/.next/standalone/.next/static/chunks/3p_pwu5gx2i9h.js +1 -0
  191. package/.next/standalone/.next/static/chunks/3yc3rzp4dxvyo.js +1 -0
  192. package/.next/standalone/.next/static/chunks/3zw8bv7-mc4n6.js +33 -0
  193. package/.next/standalone/CHANGELOG.md +63 -0
  194. package/.next/standalone/app/api/network/route.js +11 -0
  195. package/.next/standalone/app/api/scraper/route.js +19 -0
  196. package/.next/standalone/app/api/social/route.js +96 -0
  197. package/.next/standalone/app/components/AutoScanButton.js +41 -0
  198. package/.next/standalone/app/components/ChainView.js +7 -0
  199. package/.next/standalone/app/components/FilterPanel.js +37 -5
  200. package/.next/standalone/app/components/OrbitGraph.js +17 -4
  201. package/.next/standalone/app/components/ScanStatusBar.js +96 -0
  202. package/.next/standalone/app/components/SeparationView.js +9 -1
  203. package/.next/standalone/app/components/Sidebar.js +88 -26
  204. package/.next/standalone/app/components/SocialAutoSync.js +34 -0
  205. package/.next/standalone/app/components/settings/NetworkHealthSection.js +85 -0
  206. package/.next/standalone/app/globals.css +4 -0
  207. package/.next/standalone/app/layout.js +6 -0
  208. package/.next/standalone/app/page.js +37 -4
  209. package/.next/standalone/app/scores/page.js +3 -0
  210. package/.next/standalone/app/setup/page.js +13 -1
  211. package/.next/standalone/app/social/page.js +293 -0
  212. package/.next/standalone/lib/experimental-client.js +38 -0
  213. package/.next/standalone/lib/insights.js +54 -0
  214. package/.next/standalone/lib/linkedin-export.js +156 -0
  215. package/.next/standalone/lib/network-health.js +42 -0
  216. package/.next/standalone/lib/network.js +1 -0
  217. package/.next/standalone/lib/scraper-client.js +1 -0
  218. package/.next/standalone/package-lock.json +2 -2
  219. package/.next/standalone/package.json +1 -1
  220. package/.next/standalone/scripts/scrape.py +335 -5
  221. package/.next/static/chunks/04a3zockahetx.js +1 -0
  222. package/.next/static/chunks/0a5iadnmt9u9k.js +1 -0
  223. package/.next/static/chunks/{2p6o90z0vgcwr.css → 0bo1dxn5gwzpk.css} +1 -1
  224. package/.next/static/chunks/0g_p4p7oci18u.js +1 -0
  225. package/.next/static/chunks/{0ax_9zzboyv6d.js → 17-rhp8sg4m1b.js} +2 -2
  226. package/.next/static/chunks/1ra4cwcp75tfa.js +1 -0
  227. package/.next/static/chunks/236f0jtp68j_h.js +1 -0
  228. package/.next/static/chunks/24m745q_85u34.js +6 -0
  229. package/.next/static/chunks/2_wcobfqry2uj.js +1 -0
  230. package/.next/static/chunks/2dwtoszeq91us.js +1 -0
  231. package/.next/static/chunks/2hvcevue9aro4.js +1 -0
  232. package/.next/static/chunks/2uo9i03_05rqi.js +18 -0
  233. package/.next/static/chunks/3p_pwu5gx2i9h.js +1 -0
  234. package/.next/static/chunks/3yc3rzp4dxvyo.js +1 -0
  235. package/.next/static/chunks/3zw8bv7-mc4n6.js +33 -0
  236. package/CHANGELOG.md +63 -0
  237. package/lib/experimental-client.js +38 -0
  238. package/lib/insights.js +54 -0
  239. package/lib/linkedin-export.js +156 -0
  240. package/lib/network-health.js +42 -0
  241. package/lib/network.js +1 -0
  242. package/lib/scraper-client.js +1 -0
  243. package/package.json +1 -1
  244. package/scripts/scrape.py +335 -5
  245. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h1_f_2._.js +0 -3
  246. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19ip1qq._.js +0 -3
  247. package/.next/standalone/.next/server/chunks/ssr/_06w_gl4._.js +0 -3
  248. package/.next/standalone/.next/server/chunks/ssr/_13kzrwv._.js +0 -3
  249. package/.next/standalone/.next/server/chunks/ssr/_15xuwkv._.js +0 -3
  250. package/.next/standalone/.next/server/chunks/ssr/_1hgd5dp._.js +0 -3
  251. package/.next/standalone/.next/server/chunks/ssr/_1vpfyd-._.js +0 -3
  252. package/.next/standalone/.next/server/chunks/ssr/_20w06i-._.js +0 -3
  253. package/.next/standalone/.next/server/chunks/ssr/lib_1bn38gs._.js +0 -3
  254. package/.next/standalone/.next/server/chunks/ssr/node_modules_1f8t_rp._.js +0 -23
  255. package/.next/standalone/.next/static/chunks/049vavjsmqgmn.js +0 -33
  256. package/.next/standalone/.next/static/chunks/0cuuo2jwc9h94.js +0 -1
  257. package/.next/standalone/.next/static/chunks/0ljxa-wk_iu__.js +0 -18
  258. package/.next/standalone/.next/static/chunks/0mp9b72fm-jp6.js +0 -6
  259. package/.next/standalone/.next/static/chunks/1pre1cotntk6-.js +0 -1
  260. package/.next/standalone/.next/static/chunks/22x2_r768-u5i.js +0 -1
  261. package/.next/standalone/.next/static/chunks/28bu26ausqzmn.js +0 -1
  262. package/.next/standalone/.next/static/chunks/2jz311xl1pp_8.js +0 -1
  263. package/.next/standalone/.next/static/chunks/2ot61qsv6_0eq.js +0 -1
  264. package/.next/standalone/.next/static/chunks/2xjho0lroiwlq.js +0 -1
  265. package/.next/standalone/.next/static/chunks/3zs_6-nn0dapb.js +0 -1
  266. package/.next/static/chunks/049vavjsmqgmn.js +0 -33
  267. package/.next/static/chunks/0cuuo2jwc9h94.js +0 -1
  268. package/.next/static/chunks/0ljxa-wk_iu__.js +0 -18
  269. package/.next/static/chunks/0mp9b72fm-jp6.js +0 -6
  270. package/.next/static/chunks/1pre1cotntk6-.js +0 -1
  271. package/.next/static/chunks/22x2_r768-u5i.js +0 -1
  272. package/.next/static/chunks/28bu26ausqzmn.js +0 -1
  273. package/.next/static/chunks/2jz311xl1pp_8.js +0 -1
  274. package/.next/static/chunks/2ot61qsv6_0eq.js +0 -1
  275. package/.next/static/chunks/2xjho0lroiwlq.js +0 -1
  276. package/.next/static/chunks/3zs_6-nn0dapb.js +0 -1
  277. /package/.next/standalone/.next/static/{wGVrXBkFLmEB4snfhtOcO → VE8LeJXfD2H0_OgJxuJcm}/_buildManifest.js +0 -0
  278. /package/.next/standalone/.next/static/{wGVrXBkFLmEB4snfhtOcO → VE8LeJXfD2H0_OgJxuJcm}/_clientMiddlewareManifest.js +0 -0
  279. /package/.next/standalone/.next/static/{wGVrXBkFLmEB4snfhtOcO → VE8LeJXfD2H0_OgJxuJcm}/_ssgManifest.js +0 -0
  280. /package/.next/static/{wGVrXBkFLmEB4snfhtOcO → VE8LeJXfD2H0_OgJxuJcm}/_buildManifest.js +0 -0
  281. /package/.next/static/{wGVrXBkFLmEB4snfhtOcO → VE8LeJXfD2H0_OgJxuJcm}/_clientMiddlewareManifest.js +0 -0
  282. /package/.next/static/{wGVrXBkFLmEB4snfhtOcO → VE8LeJXfD2H0_OgJxuJcm}/_ssgManifest.js +0 -0
@@ -0,0 +1,96 @@
1
+ 'use client';
2
+
3
+ // The scan status bar: while a scan runs, every page shows what it's doing,
4
+ // for whom, how far it's got, and today's LinkedIn budget, with Stop. Stop
5
+ // saves what was read, and the Scan page's Resume carries on from that page.
6
+ // It sits just under the header's tabs (Network Circle, Degrees…), on every
7
+ // page, the Scan page too (Blake, 2026-09-29). It only reports: the
8
+ // pacing and the caps live in the scanner (scripts/scrape.py, lib/linkedin-limits.js).
9
+
10
+ import { useEffect, useState, useSyncExternalStore } from 'react';
11
+ import Link from 'next/link';
12
+ import { watchScanner, scannerNow, stopScrape } from '../../lib/scraper-client';
13
+ import { watchAllDay, allDayNow } from '../../lib/experimental-client';
14
+
15
+ const WHAT = {
16
+ full: 'Scanning your network',
17
+ refresh: 'Checking for new connections',
18
+ bridge: 'Scanning a circle',
19
+ resume: 'Carrying on with a circle',
20
+ 'resume-all': 'Carrying on with circles',
21
+ 'auto-bridge': 'Auto-Bridge',
22
+ 'auto-bridge-retry': 'Auto-Bridge',
23
+ rescrape: 'Re-scanning a circle',
24
+ login: 'Signing in to LinkedIn',
25
+ setup: 'Setting up the scanner',
26
+ company: 'Scanning a company',
27
+ photos: 'Saving photos',
28
+ messages: 'Reading your messages list',
29
+ };
30
+
31
+ // Green while there's plenty left, amber past 60%, red past 90%.
32
+ const tone = (used, cap) => (!cap ? '#8b9a9a' : used / cap >= 0.9 ? '#ff6b6b' : used / cap >= 0.6 ? '#FFD700' : '#00ff88');
33
+
34
+ export default function ScanStatusBar() {
35
+ const [job, setJob] = useState(() => scannerNow());
36
+ const [stopping, setStopping] = useState(false);
37
+ // The Scan page's experimental all-day Auto-Bridge switch: with it on, the bar
38
+ // stays under the tabs even while nothing runs, so you can see the mode is on.
39
+ const allDay = useSyncExternalStore(watchAllDay, allDayNow, () => false);
40
+ useEffect(() => watchScanner(() => {
41
+ const now = scannerNow();
42
+ setJob(now);
43
+ if (!now.running) setStopping(false);
44
+ }), []);
45
+ const place = {
46
+ position: 'fixed', left: '50%', top: 'var(--scan-bar-top, 94px)', transform: 'translateX(-50%)', zIndex: 60,
47
+ maxWidth: 'calc(100vw - 32px)', display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'nowrap', whiteSpace: 'nowrap',
48
+ padding: '8px 12px', borderRadius: 12, fontSize: 12, color: '#cfd8d8', background: 'rgba(14,16,32,0.94)',
49
+ };
50
+ if (!job?.running) {
51
+ if (!allDay) return null;
52
+ return (
53
+ <div role="status" style={{ ...place, opacity: 0.55, border: '1px solid rgba(255,255,255,0.1)' }}>
54
+ <span style={{ width: 8, height: 8, borderRadius: '50%', background: '#556', flexShrink: 0 }} />
55
+ <b style={{ color: '#b8c4c4' }}>Auto scan</b>
56
+ <span style={{ color: '#8b9a9a' }}>ready · press Auto scan beside Scan to start</span>
57
+ </div>
58
+ );
59
+ }
60
+
61
+ const p = job.progress;
62
+ const step = p?.kind === 'batch' && p.total ? `${p.current || p.done || 0} of ${p.total}`
63
+ : p?.kind === 'walk' && p.total ? `${(p.done || 0).toLocaleString()} of ${p.total.toLocaleString()} read`
64
+ : p?.kind === 'saving' ? 'saving' : null;
65
+ const b = job.budget;
66
+ const last = [...(job.log || [])].reverse().find((l) => l && l.trim())?.trim();
67
+
68
+ return (
69
+ <div role="status" aria-live="polite" style={{ ...place, border: '1px solid rgba(0,255,136,0.25)' }}>
70
+ <span style={{ width: 8, height: 8, borderRadius: '50%', background: '#00ff88', flexShrink: 0 }} />
71
+ <b style={{ color: '#fff' }}>{WHAT[job.action] || 'Scanning'}</b>
72
+ {job.target?.name && <span>{job.target.name}</span>}
73
+ {step && <span style={{ color: '#8b9a9a' }}>{step}</span>}
74
+ {b && (
75
+ <span style={{ color: tone(b.searches, b.cap) }} title="Searches on your LinkedIn account in the last 24 hours, and your daily budget">
76
+ {b.searches}{b.cap ? ` of ${b.cap}` : ''} searches today
77
+ </span>
78
+ )}
79
+ {last && (
80
+ <span style={{ color: '#667', flex: '0 1 240px', minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={last}>
81
+ {last}
82
+ </span>
83
+ )}
84
+ <Link href="/setup" style={{ color: '#3498DB', textDecoration: 'none' }}>Details</Link>
85
+ <button
86
+ onClick={async () => { setStopping(true); try { await stopScrape(); } catch { setStopping(false); } }}
87
+ disabled={stopping}
88
+ title="Stops after saving what's been read; Resume on the Scan page carries on from the same page"
89
+ style={{
90
+ padding: '4px 10px', borderRadius: 8, fontSize: 12, cursor: stopping ? 'default' : 'pointer',
91
+ background: 'rgba(255,107,107,0.12)', color: '#ff9b9b', border: '1px solid rgba(255,107,107,0.35)',
92
+ }}
93
+ >{stopping ? 'Stopping…' : 'Stop'}</button>
94
+ </div>
95
+ );
96
+ }
@@ -66,7 +66,7 @@ function subline(person) {
66
66
  return person.headline || person.role || person.company || '';
67
67
  }
68
68
 
69
- export default function SeparationView({ connections = [], degree2 = [], fullDegree1 = connections, fullDegree2 = degree2, onSelect, userName, selectedId, tierColors = CLASSIC }) {
69
+ export default function SeparationView({ connections = [], degree2 = [], fullDegree1 = connections, fullDegree2 = degree2, onSelect, userName, selectedId, tierColors = CLASSIC, preset = null }) {
70
70
  const isMobile = useIsMobile();
71
71
  const ROW_H = isMobile ? 68 : 56;
72
72
  const K = isMobile ? 5 : 10;
@@ -78,6 +78,14 @@ export default function SeparationView({ connections = [], degree2 = [], fullDeg
78
78
  const [tier, setTier] = useState('all');
79
79
  // Rarity beside the tier (lib/rarity.js): any bands switched on; none is everyone.
80
80
  const [rarities, setRarities] = useState(() => new Set());
81
+ // Filters a card's Insights asked for ("Show them", "S only"): applied once each.
82
+ const [presetFor, setPresetFor] = useState(null);
83
+ if (preset && preset.id !== presetFor) {
84
+ setPresetFor(preset.id);
85
+ setQuery(preset.query || '');
86
+ setTier(preset.tier || 'all');
87
+ setRarities(new Set(preset.rarity ? [preset.rarity] : []));
88
+ }
81
89
  const requests = useRequests();
82
90
  const [scrollY, setScrollY] = useState(0);
83
91
  const [box, setBox] = useState({ w: 0, h: 800 });
@@ -12,10 +12,11 @@ import { routeIndex, routesFor } from '../../lib/separation';
12
12
  import { topCompanies } from '../../lib/scoring';
13
13
  import { reachIndex, reachState, circleState, readyToScan, circleScanCost } from '../../lib/reach';
14
14
  import { exclusiveReach, bridgeOverlap } from '../../lib/brokerage';
15
+ import { profileInsights } from '../../lib/insights';
15
16
  import Avatar from './Avatar';
16
17
  import { localPhoto } from '../../lib/photos';
17
18
 
18
- export default function Sidebar({ selected, stats, tierColors, connections, degree2 = [], mode, collapsed, filter, pending = [], onToggle, onSelect, onSwitchMode, onFocusNode, onMarkSent, onUndoPending, scanNotes, csvSource = null }) {
19
+ export default function Sidebar({ selected, stats, tierColors, connections, degree2 = [], mode, collapsed, filter, pending = [], onToggle, onSelect, onSwitchMode, onFocusNode, onMarkSent, onUndoPending, scanNotes, csvSource = null, onOpenCircle, onShowInSeparation }) {
19
20
  const isDegreesMode = mode === 'degrees';
20
21
  // 'sample' or 'csv' while the sample or a CSV import is open in this window,
21
22
  // null for your own network. Only your own can be scanned: the scanner looks
@@ -43,6 +44,9 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
43
44
  // Who only each connection reaches (lib/brokerage.js), for their card.
44
45
  const exclusive = useMemo(() => exclusiveReach(degree2, connections), [degree2, connections]);
45
46
  const overlap = useMemo(() => bridgeOverlap(degree2, connections), [degree2, connections]);
47
+ const insightReach = useMemo(() => reachIndex(connections, degree2, scanNotes), [connections, degree2, scanNotes]);
48
+ // The Insights panel on a connection's card: open or closed, kept as you move between cards.
49
+ const [showInsights, setShowInsights] = useState(false);
46
50
  const bridgeById = useMemo(() => new Map(connections.map(c => [c.id, c])), [connections]);
47
51
  // Who you've asked, shared with every view: a request sent here shows in
48
52
  // Separation, the circle and the Outlink queue at once (lib/requests-client.js).
@@ -143,33 +147,91 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
143
147
  />
144
148
  )}
145
149
 
146
- {selected.degree !== 2 && (exclusive.get(selected.id)?.only > 0 || (overlap.get(selected.id)?.share >= 0.15 && overlap.get(selected.id)?.shared >= 3)) && (() => {
147
- const ex = exclusive.get(selected.id);
148
- const ov = overlap.get(selected.id);
149
- // A twin only when it's real: at least 15% of either's people, and 3 of them.
150
- const twin = ov && ov.share >= 0.15 && ov.shared >= 3 && connections.find((c) => c.id === ov.with);
150
+ {selected.degree !== 2 && (() => {
151
+ const i = profileInsights(selected, { degree2, exclusive, overlap, reach: insightReach });
152
+ if (!i) return null;
151
153
  const first = selected.name?.split(' ')[0] || 'them';
154
+ const twin = i.twin && connections.find((c) => c.id === i.twin.id);
155
+ const row = { display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 10, padding: '7px 0', borderTop: '1px solid rgba(255,255,255,0.05)' };
156
+ const label = { fontSize: 10.5, color: '#8b9a9a', minWidth: 92 };
157
+ const act = { background: 'none', border: 'none', padding: 0, color: '#3498DB', cursor: 'pointer', fontSize: 11.5, whiteSpace: 'nowrap' };
158
+ const scanned = i.bars == null ? 'Not scanned yet' : i.bars === 5 ? 'Their whole list' : `About ${i.bars * 20}% of their list`;
152
159
  return (
153
- <div style={{
154
- background: 'rgba(0,255,136,0.05)', border: '1px solid rgba(0,255,136,0.2)',
155
- borderRadius: 8, padding: 12, marginBottom: 16,
156
- }}>
157
- <div style={{ fontSize: 10, color: '#00ff88', fontWeight: 700, marginBottom: 4, letterSpacing: 1 }}>
158
- {ex?.only > 0 ? `ONLY THROUGH ${first.toUpperCase()}` : `${first.toUpperCase()}’S CIRCLE`}
159
- </div>
160
- {ex?.only > 0 && (
161
- <div style={{ fontSize: 12, color: '#ccc', lineHeight: 1.5 }}>
162
- <strong style={{ color: '#00ff88' }}>{ex.only.toLocaleString()}</strong> of the {ex.total.toLocaleString()} people in {first}&rsquo;s
163
- circle are reached by none of your other connections. {first} is your only way to them.
164
- </div>
165
- )}
166
- {twin && (
167
- <div style={{ fontSize: 12, color: '#aab', lineHeight: 1.5, marginTop: ex?.only > 0 ? 6 : 0 }}>
168
- Opens the same doors as{' '}
169
- <button onClick={() => onSelect?.(twin)} style={{ background: 'none', border: 'none', padding: 0, color: '#fff', fontWeight: 700, cursor: 'pointer', fontSize: 12 }}>
170
- {twin.name}
171
- </button>
172
- : {Math.round(ov.share * 100)}% of the people either reaches, both do ({ov.shared.toLocaleString()}).
160
+ <div style={{ border: '1px solid rgba(52,152,219,0.25)', borderRadius: 8, marginBottom: 16, background: 'rgba(52,152,219,0.04)' }}>
161
+ <button onClick={() => setShowInsights((v) => !v)} aria-expanded={showInsights}
162
+ style={{ width: '100%', display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '9px 12px', background: 'none', border: 'none', cursor: 'pointer', color: '#cfe6f7' }}>
163
+ <span style={{ fontSize: 11, fontWeight: 700, letterSpacing: 1 }}>INSIGHTS</span>
164
+ <span style={{ fontSize: 11, color: '#8b9a9a' }}>
165
+ {!showInsights && i.circle > 0 && `${i.circle.toLocaleString()} in their circle${i.only ? ` · ${i.only.toLocaleString()} only through ${first}` : ''} `}
166
+ {showInsights ? '▲' : '▼'}
167
+ </span>
168
+ </button>
169
+ {showInsights && (
170
+ <div style={{ padding: '0 12px 8px', fontSize: 12, color: '#ccc' }}>
171
+ <div style={row}>
172
+ <span style={label}>Their circle</span>
173
+ <span style={{ flex: 1 }}>{i.circle ? `${i.circle.toLocaleString()} people` : 'None scanned yet'}</span>
174
+ {i.circle > 0 && onOpenCircle && <button style={act} onClick={() => onOpenCircle(selected.id)}>Explore →</button>}
175
+ </div>
176
+ <div style={row}>
177
+ <span style={label}>Only through {first}</span>
178
+ <span style={{ flex: 1 }}>
179
+ {i.circle ? <><b style={{ color: '#00ff88' }}>{i.only.toLocaleString()}</b> ({Math.round(i.onlyShare * 100)}%): none of your other connections reach them</> : '—'}
180
+ </span>
181
+ {i.only > 0 && onShowInSeparation && (
182
+ <button style={act} onClick={() => onShowInSeparation({ query: selected.name, rarity: 'only' })}>Show them →</button>
183
+ )}
184
+ </div>
185
+ <div style={row}>
186
+ <span style={label}>Closest overlap</span>
187
+ <span style={{ flex: 1 }}>
188
+ {twin ? <>Opens the same doors as <b style={{ color: '#fff' }}>{twin.name}</b> ({Math.round(i.twin.share * 100)}%, {i.twin.shared.toLocaleString()} people)</> : 'No one opens the same doors'}
189
+ </span>
190
+ {twin && <button style={act} onClick={() => onSelect?.(twin)}>Open →</button>}
191
+ </div>
192
+ <div style={row}>
193
+ <span style={label}>Tier mix</span>
194
+ <span style={{ flex: 1, display: 'flex', height: 8, borderRadius: 4, overflow: 'hidden', background: 'rgba(255,255,255,0.05)', alignSelf: 'center' }}>
195
+ {i.circle > 0 && ['S', 'A', 'B', 'C', 'D'].map((t) => i.mix[t] > 0 && (
196
+ <span key={t} title={`${t}: ${i.mix[t]}`} style={{ width: `${(100 * i.mix[t]) / i.circle}%`, background: tierColors[t] }} />
197
+ ))}
198
+ </span>
199
+ <span style={{ fontSize: 10.5, color: '#8b9a9a' }}>{i.circle ? `${i.mix.S} S · ${i.mix.A} A` : ''}</span>
200
+ {i.mix.S > 0 && onShowInSeparation && (
201
+ <button style={act} onClick={() => onShowInSeparation({ query: selected.name, tier: 'S' })}>S only →</button>
202
+ )}
203
+ </div>
204
+ {i.companies.length > 0 && (
205
+ <div style={row}>
206
+ <span style={label}>Where they work</span>
207
+ <span style={{ flex: 1 }}>{i.companies.map((c) => `${c.count} at ${c.name}`).join(' · ')}</span>
208
+ </div>
209
+ )}
210
+ <div style={row}>
211
+ <span style={label}>Scanned</span>
212
+ <span style={{ flex: 1 }}>{scanned}</span>
213
+ {i.bars !== 5 && canScan && (
214
+ <a href={`/setup?scan=${encodeURIComponent(selected.id)}`} style={act}>{i.bars == null ? 'Scan →' : 'Finish →'}</a>
215
+ )}
216
+ </div>
217
+ {(() => {
218
+ const since = selected.connected_date ? new Date(selected.connected_date) : null;
219
+ const asked = (bridgeMap[selected.id] || []).filter((p) => hasRequest(p, requests)).length;
220
+ if (!(since && !Number.isNaN(since.getTime())) && !asked) return null;
221
+ return (
222
+ <div style={row}>
223
+ <span style={label}>You and {first}</span>
224
+ <span style={{ flex: 1 }}>
225
+ {since && !Number.isNaN(since.getTime()) ? `Connected ${since.toLocaleDateString(undefined, { month: 'short', year: 'numeric' })}` : ''}
226
+ {asked ? `${since ? ' · ' : ''}${asked} request${asked === 1 ? '' : 's'} out through them` : ''}
227
+ </span>
228
+ </div>
229
+ );
230
+ })()}
231
+ <div style={row}>
232
+ <span style={label}>Rank</span>
233
+ <span style={{ flex: 1 }}>{i.rank ? `#${i.rank} of ${i.of} by who only they reach` : 'Ranked once their circle is scanned'}</span>
234
+ </div>
173
235
  </div>
174
236
  )}
175
237
  </div>
@@ -0,0 +1,34 @@
1
+ 'use client';
2
+
3
+ // The Social tab's "once a day" live messages sync (experimental): once per
4
+ // app session, if it's switched on, the last sync is over a day old, it's
5
+ // daytime and the scanner is free, read the messages list (scripts/scrape.py
6
+ // --messages). Nothing else starts it.
7
+
8
+ import { useEffect } from 'react';
9
+ import { watchScanner, scannerNow, beginScrape } from '../../lib/scraper-client';
10
+ import { IS_DEMO } from '../../lib/demo';
11
+
12
+ let tried = false;
13
+
14
+ export default function SocialAutoSync() {
15
+ useEffect(() => {
16
+ if (IS_DEMO || tried) return undefined;
17
+ let stop = () => {};
18
+ fetch('/api/social').then((r) => r.json()).then(({ social }) => {
19
+ if (!social?.autoSync || tried) return;
20
+ const last = social.liveAt ? Date.parse(social.liveAt) : 0;
21
+ const hour = new Date().getHours();
22
+ if (Date.now() - last < 24 * 3600 * 1000 || hour < 9 || hour >= 19) return;
23
+ stop = watchScanner(() => {
24
+ const now = scannerNow();
25
+ if (!now.known || tried) return;
26
+ tried = true;
27
+ stop();
28
+ if (!now.running) beginScrape('messages').catch(() => {});
29
+ });
30
+ }).catch(() => {});
31
+ return () => stop();
32
+ }, []);
33
+ return null;
34
+ }
@@ -0,0 +1,85 @@
1
+ 'use client';
2
+
3
+ // Network health on the Scores tab (lib/network-health.js): how much of your
4
+ // 2nd degree you reach two or more ways, the effective size of your own
5
+ // network from the ties scans have kept, and your top five connections by who
6
+ // only they reach. Only your own numbers, never a percentile against others.
7
+
8
+ import { useEffect, useState } from 'react';
9
+ import Link from 'next/link';
10
+ import { useUser } from '../UserProvider';
11
+ import { loadNetwork } from '../../../lib/network';
12
+ import { networkHealth } from '../../../lib/network-health';
13
+ import { Body, LINE } from '../ui';
14
+
15
+ const TIER = { S: '#FFD700', A: '#9B59B6', B: '#3498DB', C: '#95A5A6', D: '#BDC3C7' };
16
+ const pct = (x) => `${Math.round(x * 100)}%`;
17
+
18
+ export default function NetworkHealthSection() {
19
+ const { userId } = useUser();
20
+ const [health, setHealth] = useState(null);
21
+ const [failed, setFailed] = useState(false);
22
+ useEffect(() => {
23
+ if (!userId) return undefined;
24
+ let live = true;
25
+ loadNetwork(userId)
26
+ .then((net) => { if (live) setHealth(networkHealth(net)); })
27
+ .catch(() => { if (live) setFailed(true); });
28
+ return () => { live = false; };
29
+ }, [userId]);
30
+
31
+ const box = { padding: '12px 14px', borderRadius: 8, border: LINE, background: 'rgba(255,255,255,0.03)' };
32
+ return (
33
+ <section id="health" style={{ marginTop: 20 }}>
34
+ <h2 style={{ fontSize: 16, margin: '0 0 6px' }}>Network health</h2>
35
+ {failed && <Body>Couldn&rsquo;t read your network just now.</Body>}
36
+ {!failed && !health && <Body>Reading your network…</Body>}
37
+ {health && health.circles === 0 && (
38
+ <Body>Scan a few of your connections&rsquo; circles (the Scan page, step 4) and this shows how your network holds together.</Body>
39
+ )}
40
+ {health && health.circles > 0 && (
41
+ <>
42
+ {health.early && (
43
+ <div style={{ fontSize: 12.5, color: '#FFD700', margin: '4px 0 10px' }}>
44
+ An early estimate: {health.circles} {health.circles === 1 ? 'circle' : 'circles'} scanned. These get sharper as you scan more.
45
+ </div>
46
+ )}
47
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10 }}>
48
+ <div style={box}>
49
+ <div style={{ fontSize: 22, fontWeight: 700 }}>{pct(health.twoWays)}</div>
50
+ <div style={{ fontSize: 12.5, color: '#b8c4c4' }}>of your 2nd degree you reach two or more ways</div>
51
+ <div style={{ fontSize: 11.5, color: '#778', marginTop: 4 }}>Higher is safer: lose one connection and they&rsquo;re still reachable.</div>
52
+ </div>
53
+ <div style={box}>
54
+ <div style={{ fontSize: 22, fontWeight: 700 }}>{Math.round(health.effective.size).toLocaleString()}</div>
55
+ <div style={{ fontSize: 12.5, color: '#b8c4c4' }}>
56
+ effective reach of your {health.effective.n.toLocaleString()} connections ({pct(health.effective.efficiency)} non-overlapping)
57
+ </div>
58
+ <div style={{ fontSize: 11.5, color: '#778', marginTop: 4 }}>
59
+ From {health.effective.ties.toLocaleString()} {health.effective.ties === 1 ? 'tie' : 'ties'} between your connections seen in circle scans so far,
60
+ so it can only come down as you scan more.
61
+ </div>
62
+ </div>
63
+ </div>
64
+ {health.top.length > 0 && (
65
+ <div style={{ ...box, marginTop: 10, padding: 0 }}>
66
+ <div style={{ padding: '10px 14px', fontSize: 12.5, color: '#b8c4c4', borderBottom: LINE }}>Your connections who reach the most people no one else does</div>
67
+ {health.top.map((b, k) => (
68
+ <div key={b.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 14px', borderBottom: k < health.top.length - 1 ? '1px solid rgba(255,255,255,0.04)' : 'none', fontSize: 13 }}>
69
+ <span style={{ width: 22, color: '#778' }}>#{k + 1}</span>
70
+ <span style={{ width: 8, height: 8, borderRadius: '50%', background: TIER[b.tier] || '#667', flexShrink: 0 }} />
71
+ <span style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
72
+ <b>{b.name}</b>{b.company ? <span style={{ color: '#8b9a9a' }}> · {b.company}</span> : null}
73
+ </span>
74
+ <span style={{ color: '#b8c4c4', whiteSpace: 'nowrap' }}>{b.circle.toLocaleString()} in circle</span>
75
+ <span style={{ color: '#00ff88', whiteSpace: 'nowrap' }}>{b.only.toLocaleString()} only them ({pct(b.share)})</span>
76
+ <Link href={`/?chain=${encodeURIComponent(b.id)}`} style={{ color: '#3498DB', textDecoration: 'none', whiteSpace: 'nowrap' }}>Explore →</Link>
77
+ </div>
78
+ ))}
79
+ </div>
80
+ )}
81
+ </>
82
+ )}
83
+ </section>
84
+ );
85
+ }
@@ -38,3 +38,7 @@ html:has([data-field-question]) {
38
38
  background: rgba(255, 255, 255, 0.15);
39
39
  border-radius: 3px;
40
40
  }
41
+
42
+ /* Where the scan status bar sits: just under the header (app/components/ScanStatusBar.js). */
43
+ :root { --scan-bar-top: 94px; }
44
+ @media (max-width: 767px) { :root { --scan-bar-top: 66px; } }
@@ -19,6 +19,8 @@ export const metadata = {
19
19
 
20
20
  import UserProvider from './components/UserProvider';
21
21
  import StaleServerBanner from './components/StaleServerBanner';
22
+ import ScanStatusBar from './components/ScanStatusBar';
23
+ import SocialAutoSync from './components/SocialAutoSync';
22
24
 
23
25
  export default function RootLayout({ children }) {
24
26
  return (
@@ -30,6 +32,10 @@ export default function RootLayout({ children }) {
30
32
  is running. Every page, because the answer to "did my update
31
33
  work?" should not require finding the right screen. */}
32
34
  <StaleServerBanner />
35
+ {/* While a scan runs: what it's doing and today's budget, with Stop. */}
36
+ <ScanStatusBar />
37
+ {/* The Social tab's once-a-day messages sync, when switched on. */}
38
+ <SocialAutoSync />
33
39
  </UserProvider>
34
40
  </body>
35
41
  </html>
@@ -8,6 +8,7 @@ import useRequests from './components/useRequests';
8
8
  import { requestCount } from '../lib/requests-client';
9
9
  import { loadNetwork } from '../lib/network';
10
10
  import { VIEWS, resolveView } from './components/views';
11
+ import AutoScanButton from './components/AutoScanButton';
11
12
  import { peopleByDegree, tierCountsOf } from '../lib/degrees';
12
13
  import Sidebar from './components/Sidebar';
13
14
  import FilterPanel from './components/FilterPanel';
@@ -73,6 +74,8 @@ function HomeInner() {
73
74
  const [degree3, setDegree3] = useState([]);
74
75
  // Which degrees Network Circle draws: your connections alone until you pick more.
75
76
  const [degrees, setDegrees] = useState([1]);
77
+ // Tiers switched off in Network Circle's Filter panel (C and D make a lot of noise).
78
+ const [hiddenTiers, setHiddenTiers] = useState([]);
76
79
  const [selected, setSelected] = useState(null);
77
80
  const focusNodeRef = useRef(null);
78
81
  const [filter, setFilter] = useState('all');
@@ -151,6 +154,19 @@ function HomeInner() {
151
154
 
152
155
  // Followed once: Bridge Chains has opened it, so leaving that view and coming
153
156
  // back, or reloading, starts from the overview.
157
+ // A card's Insights → Separation, filtered to that person's circle ("Show them", "S only").
158
+ const [separationPreset, setSeparationPreset] = useState(null);
159
+ const showInSeparation = useCallback((preset) => {
160
+ setSeparationPreset({ ...preset, id: `${preset.query}|${preset.tier || ''}|${preset.rarity || ''}|${Math.random()}` });
161
+ setMode('degrees');
162
+ setVisualMode('separation');
163
+ }, []);
164
+ // A card's Insights → that person's circle in Bridge Chains.
165
+ const openCircle = useCallback((id) => {
166
+ setChainOpen(id);
167
+ setMode('degrees');
168
+ setVisualMode('chain');
169
+ }, []);
154
170
  const chainOpened = useCallback(() => {
155
171
  setChainOpen(null);
156
172
  try { window.history.replaceState(null, '', window.location.pathname); } catch { /* the link stays */ }
@@ -170,10 +186,10 @@ function HomeInner() {
170
186
  () => (isDegreesMode ? connections : degrees.flatMap((d) => byDegree[d] || [])),
171
187
  [isDegreesMode, connections, degrees, byDegree],
172
188
  );
173
- const filtered = useMemo(
174
- () => (filter === 'all' ? networkRows : networkRows.filter(c => c.tier === filter)),
175
- [networkRows, filter],
176
- );
189
+ const filtered = useMemo(() => {
190
+ if (!isDegreesMode) return hiddenTiers.length ? networkRows.filter(c => !hiddenTiers.includes(c.tier)) : networkRows;
191
+ return filter === 'all' ? networkRows : networkRows.filter(c => c.tier === filter);
192
+ }, [isDegreesMode, networkRows, filter, hiddenTiers]);
177
193
  // Lookups built once per load. Every click re-renders this component, and the
178
194
  // three places below used to scan one list inside another — degree1.find per
179
195
  // 2nd-degree row, degree2.some per connection — about 16M comparisons, twice
@@ -282,6 +298,17 @@ function HomeInner() {
282
298
  >
283
299
  Paths
284
300
  </Link>}
301
+ {!IS_DEMO && <Link
302
+ href="/social"
303
+ title="Experimental: what your own LinkedIn data says about your relationships"
304
+ style={{
305
+ padding: isMobile ? '6px 10px' : '8px 16px', borderRadius: 6, border: 'none', fontSize: isMobile ? 11 : 13, fontWeight: 600,
306
+ background: 'rgba(255,255,255,0.06)', color: '#FFD700', textDecoration: 'none',
307
+ display: 'flex', alignItems: 'center', gap: 4,
308
+ }}
309
+ >
310
+ Social<span style={{ fontSize: 9, opacity: 0.7, marginLeft: 2 }}>beta</span>
311
+ </Link>}
285
312
  {!IS_DEMO && !csvMode && <Link
286
313
  href="/scores"
287
314
  style={{
@@ -316,6 +343,7 @@ function HomeInner() {
316
343
  >
317
344
  Scan
318
345
  </Link>}
346
+ {!IS_DEMO && !csvMode && <AutoScanButton isMobile={isMobile} />}
319
347
  </div>
320
348
  {/* Settings — also in CSV/sample mode and on phones, so updates stay reachable */}
321
349
  {!IS_DEMO && <Link
@@ -438,6 +466,8 @@ function HomeInner() {
438
466
  tierCounts={isDegreesMode ? stats?.tiers || {} : tierCountsOf(networkRows)}
439
467
  degrees={degrees}
440
468
  onDegreesChange={setDegrees}
469
+ hiddenTiers={hiddenTiers}
470
+ onHiddenTiersChange={setHiddenTiers}
441
471
  degreeCounts={{ 1: byDegree[1].length, 2: byDegree[2].length, 3: byDegree[3].length }}
442
472
  bridgeTierCounts={bridgeTierCounts}
443
473
  />
@@ -495,6 +525,7 @@ function HomeInner() {
495
525
  canScan,
496
526
  chainOpen,
497
527
  onChainOpened: chainOpened,
528
+ preset: separationPreset,
498
529
  };
499
530
  return <View {...viewProps} />;
500
531
  })()}
@@ -510,6 +541,8 @@ function HomeInner() {
510
541
  onToggle={() => setSidebarCollapsed(!sidebarCollapsed)}
511
542
  onSelect={(node) => { setSelected(node || null); }}
512
543
  onSwitchMode={(newMode) => { setMode(newMode); setFilter('all'); }}
544
+ onOpenCircle={openCircle}
545
+ onShowInSeparation={showInSeparation}
513
546
  onFocusNode={(nodeId) => { if (focusNodeRef.current) focusNodeRef.current(nodeId); }}
514
547
  csvSource={IS_DEMO ? 'sample' : csvMode ? csvSource : null}
515
548
  scanNotes={scanNotes}
@@ -12,6 +12,7 @@ import Link from 'next/link';
12
12
  import OnboardingGate from '../components/OnboardingGate';
13
13
  import SectorSection from '../components/settings/SectorSection';
14
14
  import TierSection from '../components/settings/TierSection';
15
+ import NetworkHealthSection from '../components/settings/NetworkHealthSection';
15
16
  import CompanyScores from '../components/CompanyScores';
16
17
  import { Body, LINE, FONT } from '../components/ui';
17
18
  import { IS_DEMO } from '../../lib/demo';
@@ -60,7 +61,9 @@ function ScoresInner() {
60
61
  How someone&rsquo;s power score is worked out, and the three things you can change about it:{' '}
61
62
  <a href="#sector" style={jump}>your field</a>, <a href="#tiers" style={jump}>how tiers are graded</a> and{' '}
62
63
  <a href="#companies" style={jump}>any company&rsquo;s score</a>. A change here rescores everyone.
64
+ {' '}First, <a href="#health" style={jump}>how your network holds together</a>.
63
65
  </Body>
66
+ <NetworkHealthSection />
64
67
  <SectorSection onSaved={saved} />
65
68
  <TierSection onSaved={saved} />
66
69
  <div id="companies" style={{ marginTop: 28 }}>
@@ -65,6 +65,8 @@ function SetupInner() {
65
65
  const [pages, setPages] = useRemembered('six-degrees-bridge-pages-v2', 100);
66
66
  // Also finish people mapped before, from the page each one's read stopped at.
67
67
  const [finish, setFinish] = useRemembered('six-degrees-bridge-finish', true);
68
+ // Experimental Auto-Bridge: all-day pacing and LinkedIn's own data (scripts/scrape.py --experimental).
69
+ const [experimental, setExperimental] = useRemembered('six-degrees-experimental-auto', false);
68
70
  const [error, setError] = useState(null);
69
71
  const logRef = useRef(null);
70
72
  // What's saved, for the question about your field: undefined while it
@@ -440,6 +442,16 @@ function SetupInner() {
440
442
  <input type="checkbox" checked={finish} onChange={(e) => setFinish(e.target.checked)} disabled={running} />
441
443
  Also finish people already mapped, from the page each one stopped at
442
444
  </label>
445
+ <label style={{ display: 'flex', gap: 8, alignItems: 'flex-start', fontSize: 12.5, color: '#b8c4c4', cursor: running ? 'default' : 'pointer' }}>
446
+ <input type="checkbox" checked={experimental} disabled={running} style={{ marginTop: 3 }}
447
+ onChange={(e) => { setExperimental(e.target.checked); window.dispatchEvent(new Event('six-degrees:experimental')); }} />
448
+ <span>
449
+ <b style={{ color: '#FFD700' }}>Experimental:</b> all-day pacing. Up to 10 pages in a sitting,
450
+ then a 45-minute rest; searches only from 9:00 to 19:00; at the daily budget it waits for it to
451
+ free up instead of stopping; and it saves every page. It also reads LinkedIn&rsquo;s own data beside
452
+ the page text, to fill gaps and measure how the two compare. It keeps running while the app is open.
453
+ </span>
454
+ </label>
443
455
  <div style={{ fontSize: 12, color: '#FFD700', lineHeight: 1.6 }}>
444
456
  Every page is a LinkedIn search, so it rests 20 seconds before each one and a
445
457
  minute after every 10, and a long list can take
@@ -449,7 +461,7 @@ function SetupInner() {
449
461
  what it read and stops, and carries on from that page next time. Keep batches small.
450
462
  </div>
451
463
  <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', alignItems: 'center' }}>
452
- <Btn onClick={() => run('auto-bridge', { maxBridges: batch, tiers: order === 'score' ? tiers : [], order, maxPages: pages, deeper: finish })} disabled={!canSearch || (order === 'score' && !tiers.length)} primary>
464
+ <Btn onClick={() => run('auto-bridge', { maxBridges: batch, tiers: order === 'score' ? tiers : [], order, maxPages: pages, deeper: finish, experimental })} disabled={!canSearch || (order === 'score' && !tiers.length)} primary>
453
465
  {running && s.action === 'auto-bridge' ? 'Mapping…' : 'Map 2nd degree'}
454
466
  </Btn>
455
467
  <select