six-degrees 0.7.0 → 0.8.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 (349) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
  3. package/.next/standalone/.next/build-manifest.json +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +32 -4
  5. package/.next/standalone/.next/routes-manifest.json +6 -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.nft.json +1 -1
  12. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  14. package/.next/standalone/.next/server/app/_not-found.rsc +16 -16
  15. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +16 -16
  16. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  17. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  18. package/.next/standalone/.next/server/app/api/admin-delete/route.js +1 -1
  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 +1 -1
  21. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/bridges/route.js +1 -1
  23. package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/bulk-import/route.js +1 -1
  25. package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +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/backup/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/data/csv/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/data/restore/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/delete-cluster/route.js +1 -1
  39. package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/ingest/route.js +1 -1
  41. package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/network/route.js +1 -1
  43. package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/notifications/route.js +1 -1
  45. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/outreach/route.js +3 -3
  47. package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/api/queue/route.js +1 -1
  49. package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/api/scraper/route.js +4 -4
  51. package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +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 +1 -1
  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 +18 -18
  73. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +18 -18
  74. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
  75. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +16 -16
  76. package/.next/standalone/.next/server/app/index.html +1 -1
  77. package/.next/standalone/.next/server/app/index.rsc +19 -19
  78. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +16 -16
  79. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +19 -19
  80. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  81. package/.next/standalone/.next/server/app/insights/page/app-paths-manifest.json +3 -0
  82. package/.next/standalone/.next/server/app/insights/page/build-manifest.json +20 -0
  83. package/.next/standalone/.next/server/app/insights/page/next-font-manifest.json +11 -0
  84. package/.next/standalone/.next/server/app/insights/page/react-loadable-manifest.json +1 -0
  85. package/.next/standalone/.next/server/app/insights/page/server-reference-manifest.json +4 -0
  86. package/.next/standalone/.next/server/app/insights/page.js +13 -0
  87. package/.next/standalone/.next/server/app/insights/page.js.map +5 -0
  88. package/.next/standalone/.next/server/app/insights/page.js.nft.json +1 -0
  89. package/.next/standalone/.next/server/app/insights/page_client-reference-manifest.js +3 -0
  90. package/.next/standalone/.next/server/app/insights.html +1 -0
  91. package/.next/standalone/.next/server/app/insights.meta +26 -0
  92. package/.next/standalone/.next/server/app/insights.rsc +35 -0
  93. package/.next/standalone/.next/server/app/insights.segments/_full.segment.rsc +35 -0
  94. package/.next/standalone/.next/server/app/insights.segments/_tree.segment.rsc +4 -0
  95. package/.next/standalone/.next/server/app/insights.segments/insights/__PAGE__.segment.rsc +33 -0
  96. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  97. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  98. package/.next/standalone/.next/server/app/launch.html +2 -2
  99. package/.next/standalone/.next/server/app/launch.rsc +18 -18
  100. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +18 -18
  101. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
  102. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +16 -16
  103. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  104. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  105. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  106. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  107. package/.next/standalone/.next/server/app/paths.html +1 -1
  108. package/.next/standalone/.next/server/app/paths.rsc +19 -19
  109. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +19 -19
  110. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
  111. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +16 -16
  112. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  113. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  114. package/.next/standalone/.next/server/app/profile.html +1 -1
  115. package/.next/standalone/.next/server/app/profile.rsc +19 -19
  116. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +19 -19
  117. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
  118. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +16 -16
  119. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  120. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  121. package/.next/standalone/.next/server/app/queue.html +1 -1
  122. package/.next/standalone/.next/server/app/queue.rsc +19 -19
  123. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +19 -19
  124. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
  125. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +21 -20
  126. package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
  127. package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
  128. package/.next/standalone/.next/server/app/scores.html +1 -1
  129. package/.next/standalone/.next/server/app/scores.rsc +18 -18
  130. package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +18 -18
  131. package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
  132. package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +16 -16
  133. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  134. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  135. package/.next/standalone/.next/server/app/settings.html +1 -1
  136. package/.next/standalone/.next/server/app/settings.rsc +19 -19
  137. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +19 -19
  138. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  139. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +16 -16
  140. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  141. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  142. package/.next/standalone/.next/server/app/setup.html +1 -1
  143. package/.next/standalone/.next/server/app/setup.rsc +19 -19
  144. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +19 -19
  145. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
  146. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +16 -16
  147. package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
  148. package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
  149. package/.next/standalone/.next/server/app/social.html +1 -1
  150. package/.next/standalone/.next/server/app/social.rsc +18 -18
  151. package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +18 -18
  152. package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +2 -2
  153. package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +16 -16
  154. package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
  155. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__063s0x7._.js → [root-of-the-server]__0r-0t-9._.js} +1 -1
  156. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ra6xyg._.js +15 -0
  157. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__20rwfdw._.js → [root-of-the-server]__0rcpjad._.js} +5 -2
  158. package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -7
  159. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
  160. package/.next/standalone/.next/server/chunks/lib_02iz_9q._.js +2 -2
  161. package/.next/standalone/.next/server/chunks/lib_05agvk3._.js +2 -2
  162. package/.next/standalone/.next/server/chunks/lib_0v84u66._.js +2 -2
  163. package/.next/standalone/.next/server/chunks/lib_db-client_0_dch35.js +9 -9
  164. package/.next/standalone/.next/server/chunks/{lib_rpc_0jnfp2e.js → lib_rpc_00nndai.js} +1 -1
  165. package/.next/standalone/.next/server/chunks/{lib_rpc_0b17r6v.js → lib_rpc_03ibb-x.js} +1 -1
  166. package/.next/standalone/.next/server/chunks/{lib_rpc_0doq0t-.js → lib_rpc_03ri3ji.js} +1 -1
  167. package/.next/standalone/.next/server/chunks/{lib_rpc_0fd2rlw.js → lib_rpc_078ce48.js} +1 -1
  168. package/.next/standalone/.next/server/chunks/lib_rpc_0a69mgr.js +3 -0
  169. package/.next/standalone/.next/server/chunks/{lib_rpc_005abye.js → lib_rpc_0cej3t8.js} +1 -1
  170. package/.next/standalone/.next/server/chunks/lib_rpc_0cmh4g6.js +3 -0
  171. package/.next/standalone/.next/server/chunks/lib_rpc_0nx2_pc.js +3 -0
  172. package/.next/standalone/.next/server/chunks/lib_rpc_0r79-w1.js +3 -0
  173. package/.next/standalone/.next/server/chunks/{lib_rpc_0is4qoc.js → lib_rpc_0t552z6.js} +1 -1
  174. package/.next/standalone/.next/server/chunks/lib_rpc_0yx8ynd.js +3 -0
  175. package/.next/standalone/.next/server/chunks/lib_rpc_12p-o9-.js +3 -0
  176. package/.next/standalone/.next/server/chunks/lib_rpc_1466dnb.js +3 -0
  177. package/.next/standalone/.next/server/chunks/lib_rpc_1sryxv6.js +3 -0
  178. package/.next/standalone/.next/server/chunks/lib_rpc_1trn-x5.js +3 -0
  179. package/.next/standalone/.next/server/chunks/lib_rpc_1ygu_mv.js +3 -0
  180. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0egamyq._.js +3 -0
  181. package/.next/standalone/.next/server/chunks/ssr/_04am_h8._.js +3 -0
  182. package/.next/standalone/.next/server/chunks/ssr/_07zb7lu._.js +1 -1
  183. package/.next/standalone/.next/server/chunks/ssr/_0_79idq._.js +1 -1
  184. package/.next/standalone/.next/server/chunks/ssr/_0gds903._.js +3 -0
  185. package/.next/standalone/.next/server/chunks/ssr/_0klqt9z._.js +3 -0
  186. package/.next/standalone/.next/server/chunks/ssr/{_1k9qfsk._.js → _14jak5s._.js} +6 -2
  187. package/.next/standalone/.next/server/chunks/ssr/_14nxd3q._.js +1 -1
  188. package/.next/standalone/.next/server/chunks/ssr/_1cj2yog._.js +3 -0
  189. package/.next/standalone/.next/server/chunks/ssr/_1fm1ih_._.js +1 -1
  190. package/.next/standalone/.next/server/chunks/ssr/_1hdqw0-._.js +7 -0
  191. package/.next/standalone/.next/server/chunks/ssr/_1k2nmbw._.js +1 -1
  192. package/.next/standalone/.next/server/chunks/ssr/{_0gy11ar._.js → _1r62six._.js} +6 -2
  193. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_insights_page_actions_0-0tf75.js +3 -0
  194. package/.next/standalone/.next/server/chunks/ssr/app_components_0uuff77._.js +2 -2
  195. package/.next/standalone/.next/server/chunks/ssr/app_components_1j3q-h5._.js +2 -2
  196. package/.next/standalone/.next/server/chunks/ssr/{_01o3i5y._.js → app_components_settings_SectorPicker_1c-29qw.js} +2 -6
  197. package/.next/standalone/.next/server/chunks/ssr/app_components_ui_0pdmz2z.js +3 -0
  198. package/.next/standalone/.next/server/chunks/ssr/app_insights_page_1d_chja.js +3 -0
  199. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +14 -14
  200. package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +3 -3
  201. package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
  202. package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +3 -3
  203. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  204. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  205. package/.next/standalone/.next/server/next-font-manifest.js +1 -1
  206. package/.next/standalone/.next/server/next-font-manifest.json +4 -0
  207. package/.next/standalone/.next/server/pages/404.html +1 -1
  208. package/.next/standalone/.next/server/pages/500.html +1 -1
  209. package/.next/standalone/.next/server/prefetch-hints.json +14 -0
  210. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  211. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  212. package/.next/standalone/.next/static/chunks/02wnf8dkxegxg.js +34 -0
  213. package/.next/standalone/.next/static/chunks/0838_s6w-i_ik.js +1 -0
  214. package/.next/{static/chunks/2eo9pqq9gay3r.js → standalone/.next/static/chunks/0ka4prpk3w9wn.js} +4 -4
  215. package/.next/standalone/.next/static/chunks/0s_xxwp7z88vv.js +1 -0
  216. package/.next/standalone/.next/static/chunks/1-nnl8amfm4yh.js +57 -0
  217. package/.next/standalone/.next/static/chunks/112mv-com-mqa.js +21 -0
  218. package/.next/{static/chunks/44yn0tcd9dfk3.js → standalone/.next/static/chunks/1bkt_hhjzvbh8.js} +1 -1
  219. package/.next/standalone/.next/static/chunks/1cmphb3kh2ers.css +3 -0
  220. package/.next/{static/chunks/1qbjermbf68xj.js → standalone/.next/static/chunks/1d8yv3k_atka7.js} +1 -1
  221. package/.next/standalone/.next/static/chunks/23hc51oa4vkhc.js +5 -0
  222. package/.next/standalone/.next/static/chunks/27n8k9wxlz5-7.js +1 -0
  223. package/.next/standalone/.next/static/chunks/{0p3g5tfostsep.js → 2q9i7pcxcnph1.js} +2 -2
  224. package/.next/{static/chunks/2zf4jmz9l3li2.js → standalone/.next/static/chunks/2w-y83mjlpnse.js} +1 -1
  225. package/.next/standalone/.next/static/chunks/2ws1xf293j-tm.js +5 -0
  226. package/.next/standalone/.next/static/chunks/3cqn-r-y5rrw1.js +5 -0
  227. package/.next/standalone/.next/static/chunks/3opgg1efrx8q6.js +1 -0
  228. package/.next/standalone/.next/static/chunks/3p51fpxxi4d5y.js +34 -0
  229. package/.next/{static/chunks/0an_b6zi1r_qq.js → standalone/.next/static/chunks/3y40z6of3r9py.js} +1 -5
  230. package/.next/standalone/.next/static/chunks/403dpflln8ah5.js +1 -0
  231. package/.next/standalone/CHANGELOG.md +60 -0
  232. package/.next/standalone/README.md +25 -1
  233. package/.next/standalone/SECURITY.md +8 -0
  234. package/.next/standalone/app/api/outreach/route.js +13 -73
  235. package/.next/standalone/app/api/scraper/route.js +97 -4
  236. package/.next/standalone/app/components/AppHeader.js +22 -6
  237. package/.next/standalone/app/components/AppTabs.js +4 -0
  238. package/.next/standalone/app/components/AutoConnect.js +286 -0
  239. package/.next/standalone/app/components/ChainView.js +75 -2
  240. package/.next/standalone/app/components/FilterPanel.js +8 -1
  241. package/.next/standalone/app/components/LinkedInLimits.js +38 -13
  242. package/.next/standalone/app/components/OutlinkQuest.js +32 -11
  243. package/.next/standalone/app/components/ScanStatusBar.js +18 -3
  244. package/.next/standalone/app/components/ScanTrail.js +13 -3
  245. package/.next/standalone/app/components/SeparationView.js +6 -22
  246. package/.next/standalone/app/components/Sidebar.js +147 -38
  247. package/.next/standalone/app/components/insights/Boards.js +275 -0
  248. package/.next/standalone/app/components/insights/PeopleView.js +266 -0
  249. package/.next/standalone/app/components/insights/Rail.js +156 -0
  250. package/.next/standalone/app/components/insights/Report.js +382 -0
  251. package/.next/standalone/app/components/insights/parts.js +239 -0
  252. package/.next/standalone/app/components/settings/UsageSection.js +50 -3
  253. package/.next/standalone/app/globals.css +2 -3
  254. package/.next/standalone/app/insights/page.js +220 -0
  255. package/.next/standalone/app/page.js +1 -17
  256. package/.next/standalone/app/queue/page.js +30 -18
  257. package/.next/standalone/app/setup/page.js +16 -9
  258. package/.next/standalone/lib/auto-connect.js +145 -0
  259. package/.next/standalone/lib/companies.js +1 -1
  260. package/.next/standalone/lib/in-progress.js +46 -0
  261. package/.next/standalone/lib/insights-board.js +613 -0
  262. package/.next/standalone/lib/linkedin-limits.js +36 -11
  263. package/.next/standalone/lib/requests-client.js +10 -0
  264. package/.next/standalone/lib/requests.js +124 -0
  265. package/.next/standalone/lib/scan-state.js +1 -0
  266. package/.next/standalone/lib/scraper-client.js +18 -5
  267. package/.next/standalone/lib/settings.js +3 -0
  268. package/.next/standalone/lib/usage.js +5 -0
  269. package/.next/standalone/package-lock.json +2 -2
  270. package/.next/standalone/package.json +1 -1
  271. package/.next/standalone/scripts/scrape.py +569 -9
  272. package/.next/static/chunks/02wnf8dkxegxg.js +34 -0
  273. package/.next/static/chunks/0838_s6w-i_ik.js +1 -0
  274. package/.next/{standalone/.next/static/chunks/2eo9pqq9gay3r.js → static/chunks/0ka4prpk3w9wn.js} +4 -4
  275. package/.next/static/chunks/0s_xxwp7z88vv.js +1 -0
  276. package/.next/static/chunks/1-nnl8amfm4yh.js +57 -0
  277. package/.next/static/chunks/112mv-com-mqa.js +21 -0
  278. package/.next/{standalone/.next/static/chunks/44yn0tcd9dfk3.js → static/chunks/1bkt_hhjzvbh8.js} +1 -1
  279. package/.next/static/chunks/1cmphb3kh2ers.css +3 -0
  280. package/.next/{standalone/.next/static/chunks/1qbjermbf68xj.js → static/chunks/1d8yv3k_atka7.js} +1 -1
  281. package/.next/static/chunks/23hc51oa4vkhc.js +5 -0
  282. package/.next/static/chunks/27n8k9wxlz5-7.js +1 -0
  283. package/.next/static/chunks/{0p3g5tfostsep.js → 2q9i7pcxcnph1.js} +2 -2
  284. package/.next/{standalone/.next/static/chunks/2zf4jmz9l3li2.js → static/chunks/2w-y83mjlpnse.js} +1 -1
  285. package/.next/static/chunks/2ws1xf293j-tm.js +5 -0
  286. package/.next/static/chunks/3cqn-r-y5rrw1.js +5 -0
  287. package/.next/static/chunks/3opgg1efrx8q6.js +1 -0
  288. package/.next/static/chunks/3p51fpxxi4d5y.js +34 -0
  289. package/.next/{standalone/.next/static/chunks/0an_b6zi1r_qq.js → static/chunks/3y40z6of3r9py.js} +1 -5
  290. package/.next/static/chunks/403dpflln8ah5.js +1 -0
  291. package/CHANGELOG.md +60 -0
  292. package/README.md +25 -1
  293. package/SECURITY.md +8 -0
  294. package/lib/auto-connect.js +145 -0
  295. package/lib/companies.js +1 -1
  296. package/lib/in-progress.js +46 -0
  297. package/lib/insights-board.js +613 -0
  298. package/lib/linkedin-limits.js +36 -11
  299. package/lib/requests-client.js +10 -0
  300. package/lib/requests.js +124 -0
  301. package/lib/scan-state.js +1 -0
  302. package/lib/scraper-client.js +18 -5
  303. package/lib/settings.js +3 -0
  304. package/lib/usage.js +5 -0
  305. package/package.json +1 -1
  306. package/scripts/scrape.py +569 -9
  307. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_4y8z7._.js +0 -8
  308. package/.next/standalone/.next/server/chunks/_02twlca._.js +0 -10
  309. package/.next/standalone/.next/server/chunks/lib_rpc_0nef_n7.js +0 -3
  310. package/.next/standalone/.next/server/chunks/lib_rpc_0plrzra.js +0 -3
  311. package/.next/standalone/.next/server/chunks/lib_rpc_13a4m0c.js +0 -3
  312. package/.next/standalone/.next/server/chunks/lib_rpc_1if17c-.js +0 -3
  313. package/.next/standalone/.next/server/chunks/lib_rpc_1j-zw7v.js +0 -3
  314. package/.next/standalone/.next/server/chunks/lib_rpc_1sngt-q.js +0 -3
  315. package/.next/standalone/.next/server/chunks/lib_rpc_1v2sgme.js +0 -3
  316. package/.next/standalone/.next/server/chunks/lib_rpc_1vh-tgx.js +0 -3
  317. package/.next/standalone/.next/server/chunks/lib_rpc_1zaubz7.js +0 -3
  318. package/.next/standalone/.next/server/chunks/ssr/_02sband._.js +0 -7
  319. package/.next/standalone/.next/server/chunks/ssr/_189g8lg._.js +0 -3
  320. package/.next/standalone/.next/server/chunks/ssr/_1f_n00p._.js +0 -3
  321. package/.next/standalone/.next/server/chunks/ssr/app_components_0t908t-._.js +0 -3
  322. package/.next/standalone/.next/static/chunks/0e3g0sil79cus.js +0 -21
  323. package/.next/standalone/.next/static/chunks/0zd0t7e60bn4z.js +0 -1
  324. package/.next/standalone/.next/static/chunks/163b-20vwa06i.js +0 -34
  325. package/.next/standalone/.next/static/chunks/1pa-2v1ajvrko.js +0 -1
  326. package/.next/standalone/.next/static/chunks/23_1z47al92nz.js +0 -57
  327. package/.next/standalone/.next/static/chunks/260bomtr214m8.js +0 -34
  328. package/.next/standalone/.next/static/chunks/2xk2ra4piiuod.js +0 -34
  329. package/.next/standalone/.next/static/chunks/3b0-14cxqsprt.js +0 -1
  330. package/.next/standalone/.next/static/chunks/3jarus1_38s2z.js +0 -1
  331. package/.next/standalone/.next/static/chunks/3ssu341hav_sq.css +0 -3
  332. package/.next/standalone/.next/static/chunks/3v1qd3sqhqn0d.js +0 -1
  333. package/.next/static/chunks/0e3g0sil79cus.js +0 -21
  334. package/.next/static/chunks/0zd0t7e60bn4z.js +0 -1
  335. package/.next/static/chunks/163b-20vwa06i.js +0 -34
  336. package/.next/static/chunks/1pa-2v1ajvrko.js +0 -1
  337. package/.next/static/chunks/23_1z47al92nz.js +0 -57
  338. package/.next/static/chunks/260bomtr214m8.js +0 -34
  339. package/.next/static/chunks/2xk2ra4piiuod.js +0 -34
  340. package/.next/static/chunks/3b0-14cxqsprt.js +0 -1
  341. package/.next/static/chunks/3jarus1_38s2z.js +0 -1
  342. package/.next/static/chunks/3ssu341hav_sq.css +0 -3
  343. package/.next/static/chunks/3v1qd3sqhqn0d.js +0 -1
  344. /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_buildManifest.js +0 -0
  345. /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_clientMiddlewareManifest.js +0 -0
  346. /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_ssgManifest.js +0 -0
  347. /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_buildManifest.js +0 -0
  348. /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_clientMiddlewareManifest.js +0 -0
  349. /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_ssgManifest.js +0 -0
@@ -0,0 +1,239 @@
1
+ 'use client';
2
+
3
+ // The small pieces the Insights tab is built from: tier chips with their
4
+ // letter on them (C and D are two greys too close to tell apart by colour),
5
+ // rarity tags, the 1st / 2nd chip, the person cell, cards and the line under
6
+ // a number that says where it came from. Colours are the look's own: the
7
+ // theme's dot colours (lib/themes.js TIER_COLORS, a live object) and the
8
+ // --sd-* variables, so a light look reads as well as the dark ones.
9
+
10
+ import { useEffect, useState } from 'react';
11
+ import Link from 'next/link';
12
+ import Avatar from '../Avatar';
13
+ import { TIER_COLORS, luminance } from '../../../lib/themes';
14
+ import { RARITY } from '../../../lib/rarity';
15
+ import { roleLine } from '../../../lib/insights-board';
16
+ import { markRequested } from '../../../lib/requests-client';
17
+ import ConnectChoice, { secondaryLook } from '../AutoConnect';
18
+
19
+ export const fmt = (n) => Number(n || 0).toLocaleString('en-US');
20
+ export const one = (x) => (Math.round(Number(x || 0) * 10) / 10).toFixed(1);
21
+ export const pc = (x) => `${Math.round(Number(x) || 0)}%`;
22
+ export const RARITY_BY_KEY = Object.fromEntries(RARITY.map((r) => [r.key, r]));
23
+ export const LINE = 'rgba(var(--sd-ink, 255, 255, 255), 0.08)';
24
+ export const SOFT_LINE = 'rgba(var(--sd-ink, 255, 255, 255), 0.05)';
25
+ export const CARD_BG = 'rgba(var(--sd-ink, 255, 255, 255), 0.035)';
26
+
27
+ export function useIsMobile() {
28
+ const [m, setM] = useState(false);
29
+ useEffect(() => {
30
+ const check = () => setM(window.innerWidth < 768);
31
+ check();
32
+ window.addEventListener('resize', check);
33
+ return () => window.removeEventListener('resize', check);
34
+ }, []);
35
+ return m;
36
+ }
37
+
38
+ /** Black or white, whichever reads on a colour. */
39
+ export function inkOn(hex) {
40
+ try { return luminance(hex) > 0.36 ? '#000' : '#fff'; } catch { return '#000'; }
41
+ }
42
+ export const tierColor = (tier) => TIER_COLORS[tier] || '#888888';
43
+
44
+ /** A tier as a filled chip with its letter. */
45
+ export function TierChip({ tier, size = 20 }) {
46
+ if (!tier) return null;
47
+ const c = tierColor(tier);
48
+ return (
49
+ <span aria-label={`${tier} tier`} style={{
50
+ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
51
+ minWidth: size, height: size, padding: '0 4px', borderRadius: Math.round(size / 4),
52
+ background: c, color: inkOn(c), fontSize: Math.round(size * 0.56), fontWeight: 800, lineHeight: 1,
53
+ }}>{tier}</span>
54
+ );
55
+ }
56
+
57
+ /** "1st" or "2nd". */
58
+ export function DegreeChip({ degree }) {
59
+ const first = degree === 1;
60
+ return (
61
+ <span style={{
62
+ fontSize: 10.5, fontWeight: 700, padding: '2px 6px', borderRadius: 4, letterSpacing: 0.2, flexShrink: 0, whiteSpace: 'nowrap',
63
+ background: first ? 'rgba(0,255,136,0.1)' : 'rgba(255,107,53,0.1)',
64
+ border: `1px solid ${first ? 'rgba(0,255,136,0.3)' : 'rgba(255,107,53,0.3)'}`,
65
+ color: first ? 'var(--sd-green, #7dffc0)' : 'var(--sd-orange, #ffab8a)',
66
+ }}>{first ? '1st' : '2nd'}</span>
67
+ );
68
+ }
69
+
70
+ /** How rare the way in is, and the count: "+" where it's the ways your scans saw (a floor). */
71
+ export function RarityTag({ rarity, count = true, small = false }) {
72
+ if (!rarity) return null;
73
+ const r = RARITY_BY_KEY[rarity.key];
74
+ const title = rarity.from === 'linkedin'
75
+ ? `${fmt(rarity.count)} mutual connection${rarity.count === 1 ? '' : 's'}, LinkedIn’s own count`
76
+ : `${fmt(rarity.count)} way${rarity.count === 1 ? '' : 's'} in seen in your scans, which can only go up`;
77
+ return (
78
+ <span title={title} style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: small ? 11 : 11.5, color: 'var(--sd-fg-2, #b8c4c4)', whiteSpace: 'nowrap' }}>
79
+ <i className="sd-dot-html" style={{ width: 8, height: 8, borderRadius: '50%', background: r.color, flexShrink: 0 }} />
80
+ {r.label}
81
+ {count && <span style={{ color: 'var(--sd-fg-4, #6f7a88)', fontSize: 10.5 }}>{fmt(rarity.count)}{rarity.from === 'linkedin' ? '' : '+'}</span>}
82
+ </span>
83
+ );
84
+ }
85
+
86
+ /** Face, name, tier and the role their score is built on. */
87
+ export function PersonCell({ row, size = 34, chip = true, sub }) {
88
+ const { title, company } = roleLine(row);
89
+ return (
90
+ <span style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
91
+ <Avatar person={row} size={size} tierColors={TIER_COLORS} />
92
+ <span style={{ display: 'flex', flexDirection: 'column', minWidth: 0 }}>
93
+ <b style={{ fontSize: 13.5, fontWeight: 650, color: 'var(--sd-fg-1, #fff)', ...ELLIPSIS }}>{row?.name || 'Someone'}</b>
94
+ <span style={{ display: 'flex', alignItems: 'center', gap: 5, fontSize: 11.5, color: 'var(--sd-fg-3, #8b9a9a)', minWidth: 0 }}>
95
+ {chip && <TierChip tier={row?.tier} size={15} />}
96
+ <span style={ELLIPSIS}>{sub ?? [title, company].filter(Boolean).join(' · ')}</span>
97
+ </span>
98
+ </span>
99
+ </span>
100
+ );
101
+ }
102
+
103
+ export const ELLIPSIS = { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0 };
104
+
105
+ /** The page's title, in the tab's own colours (plain ink on Analyst, which takes gradients off). */
106
+ export const TITLE = {
107
+ margin: 0, fontSize: 34, fontWeight: 850, letterSpacing: -0.6, lineHeight: 1.1,
108
+ background: 'linear-gradient(135deg, var(--sd-gold, #FFD700) 10%, var(--sd-orange, #FF6B35) 60%, var(--sd-purple, #9B59B6))',
109
+ WebkitBackgroundClip: 'text', backgroundClip: 'text', WebkitTextFillColor: 'transparent',
110
+ };
111
+
112
+ /** A board's title and the one line under it. */
113
+ export function Hero({ title, children, isMobile, right }) {
114
+ return (
115
+ <div style={{ display: 'flex', flexDirection: isMobile ? 'column' : 'row', justifyContent: 'space-between', alignItems: isMobile ? 'stretch' : 'flex-end', gap: isMobile ? 10 : 24, marginBottom: 16 }}>
116
+ <div style={{ minWidth: 0 }}>
117
+ <h1 style={{ ...TITLE, fontSize: isMobile ? 27 : 34 }}>{title}</h1>
118
+ <p style={{ margin: '5px 0 0', color: 'var(--sd-fg-2, #b8c4c4)', fontSize: isMobile ? 13 : 14, lineHeight: 1.45 }}>{children}</p>
119
+ </div>
120
+ {right}
121
+ </div>
122
+ );
123
+ }
124
+
125
+ /** A card, as the rail and the boards draw them. */
126
+ export function Card({ children, style, dashed = false, id }) {
127
+ return (
128
+ <section id={id} style={{
129
+ border: dashed ? '1px dashed rgba(255,215,0,0.35)' : `1px solid ${LINE}`, borderRadius: 14,
130
+ background: dashed ? 'rgba(255,215,0,0.04)' : CARD_BG, padding: '13px 14px 10px', minWidth: 0, ...style,
131
+ }}>{children}</section>
132
+ );
133
+ }
134
+
135
+ /** A card's title, its line under it, and a link to its board. */
136
+ export function CardHead({ title, sub, more, onMore }) {
137
+ return (
138
+ <>
139
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 8 }}>
140
+ <h3 style={{ margin: 0, fontSize: 14, fontWeight: 750, color: 'var(--sd-fg-1, #fff)' }}>{title}</h3>
141
+ {more && <button type="button" onClick={onMore} style={LINK_BTN}>{more}</button>}
142
+ </div>
143
+ {sub && <div style={{ fontSize: 11.5, color: 'var(--sd-fg-3, #8b9a9a)', margin: '2px 0 6px', lineHeight: 1.4 }}>{sub}</div>}
144
+ </>
145
+ );
146
+ }
147
+
148
+ export const LINK_BTN = { background: 'none', border: 'none', padding: 0, cursor: 'pointer', fontSize: 11.5, color: 'var(--sd-blue, #3498DB)', whiteSpace: 'nowrap', fontWeight: 600 };
149
+
150
+ /** Where a number came from, under it. */
151
+ export function Src({ children, style }) {
152
+ if (!children) return null;
153
+ return <div style={{ fontSize: 11, color: 'var(--sd-fg-4, #6f7a88)', lineHeight: 1.5, marginTop: 6, ...style }}>{children}</div>;
154
+ }
155
+
156
+ /** A row in a rail card: rank, who, and a number on the right with what it counts. */
157
+ export function MiniRow({ rank, left, value, valueColor, label, first }) {
158
+ return (
159
+ <div style={{ display: 'flex', alignItems: 'center', gap: 9, padding: '6px 0', borderTop: first ? 'none' : `1px solid ${SOFT_LINE}` }}>
160
+ {rank != null && <span style={{ width: 14, fontSize: 11.5, color: 'var(--sd-fg-4, #6f7a88)', fontWeight: 700, flexShrink: 0 }}>{rank}</span>}
161
+ <span style={{ flex: 1, minWidth: 0 }}>{left}</span>
162
+ <span style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', textAlign: 'right', flexShrink: 0 }}>
163
+ <b style={{ fontSize: 15, fontWeight: 800, color: valueColor || 'var(--sd-fg-1, #fff)', fontVariantNumeric: 'tabular-nums' }}>{value}</b>
164
+ {label && <span style={{ fontSize: 10, color: 'var(--sd-fg-4, #6f7a88)', whiteSpace: 'nowrap' }}>{label}</span>}
165
+ </span>
166
+ </div>
167
+ );
168
+ }
169
+
170
+ /** A board that needs circles, on a network with none yet. */
171
+ export function NeedsCircles({ source, compact = false }) {
172
+ const csv = source === 'csv';
173
+ return (
174
+ <div style={{ fontSize: compact ? 12 : 13, color: 'var(--sd-fg-2, #b8c4c4)', lineHeight: 1.5, padding: compact ? '4px 0 6px' : '6px 0' }}>
175
+ {csv
176
+ ? 'A CSV import is your connections only: LinkedIn’s export has no circles. Scan your own network to see who stands behind your connections.'
177
+ : 'Scan a few circles to see who stands behind your connections.'}
178
+ {!csv && source === 'own' && <> <Link href="/setup" style={{ color: 'var(--sd-green, #00ff88)', fontWeight: 700, textDecoration: 'none', whiteSpace: 'nowrap' }}>Open Scan →</Link></>}
179
+ </div>
180
+ );
181
+ }
182
+
183
+ const ACT = {
184
+ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 12, fontWeight: 700, padding: '6px 11px',
185
+ borderRadius: 7, whiteSpace: 'nowrap', textDecoration: 'none', cursor: 'pointer', flexShrink: 0,
186
+ };
187
+ const OUTLINE = { ...ACT, color: 'var(--sd-blue, #3498DB)', border: '1px solid rgba(52,152,219,0.4)', background: 'rgba(52,152,219,0.06)' };
188
+
189
+ /** Their circle in Bridge Chains: the link every page uses (/?chain=<id>). */
190
+ export function OpenCircle({ id, compact }) {
191
+ return <Link href={`/?chain=${encodeURIComponent(id)}`} style={{ ...OUTLINE, ...(compact ? { padding: '4px 8px', fontSize: 11 } : null) }}>Open circle</Link>;
192
+ }
193
+
194
+ /** The Scan page's "Scan one circle" for them (/setup?scan=<id>): it says the cost and asks first. */
195
+ export function ScanCircle({ id, compact }) {
196
+ return <Link href={`/setup?scan=${encodeURIComponent(id)}`} style={{ ...OUTLINE, ...(compact ? { padding: '4px 8px', fontSize: 11 } : null) }} title="Opens the Scan page with them picked: it says what the scan costs and starts nothing until you press it">Scan circle</Link>;
197
+ }
198
+
199
+ /**
200
+ * Ask: what the person card's Connect does. Their LinkedIn profile opens in
201
+ * a new tab to send the request there, and it's marked sent through the
202
+ * connection who is your best way in (lib/requests-client.js), everywhere at once.
203
+ * On your own network Auto stands first, the better pick, and Ask steps back
204
+ * to an outline beside it, as everywhere else Connect is offered
205
+ * (components/AutoConnect.js; Blake, 2026-10-03: "wherever its available").
206
+ */
207
+ export function AskButton({ row, bridgeId, asked, compact, canAuto = false }) {
208
+ if (asked) {
209
+ return <span title="A request is marked sent. The next check of your own connections notices when they accept." style={{ ...ACT, cursor: 'default', color: 'var(--sd-fg-4, #6f7a88)', border: `1px dashed ${LINE}`, ...(compact ? { padding: '4px 8px', fontSize: 11 } : null) }}>Asked</span>;
210
+ }
211
+ if (!row?.profile_url) return null;
212
+ const size = compact ? { padding: '4px 9px', fontSize: 11 } : null;
213
+ const ask = (second) => (
214
+ <a href={row.profile_url} target="_blank" rel="noopener noreferrer"
215
+ title="Opens their LinkedIn profile in a new tab to send the request there, and marks it sent here"
216
+ onClick={() => { markRequested(row, { bridgeId }).catch(() => {}); }}
217
+ style={second
218
+ ? { ...ACT, ...secondaryLook(compact ? 'row' : 'table'), ...size }
219
+ : { ...ACT, color: '#000', border: 'none', background: 'linear-gradient(135deg, #FF6B35, #FFD700)', ...size }}>
220
+ Ask
221
+ </a>
222
+ );
223
+ return <ConnectChoice person={row} canAuto={canAuto} size={compact ? 'row' : 'table'} connect={ask} />;
224
+ }
225
+
226
+ /** A horizontal bar made of parts: [{ value, color, label? }] over `max`. */
227
+ export function Bars({ parts, max, height = 14, gap = 2 }) {
228
+ const total = Math.max(1, max || parts.reduce((t, p) => t + p.value, 0));
229
+ return (
230
+ <span style={{ display: 'flex', gap, height, background: 'rgba(var(--sd-ink, 255, 255, 255), 0.05)', borderRadius: 4, overflow: 'hidden', minWidth: 0, flex: 1 }}>
231
+ {parts.filter((p) => p.value > 0).map((p, i) => (
232
+ <span key={i} title={p.title} style={{
233
+ width: `${(100 * p.value) / total}%`, background: p.color, display: 'flex', alignItems: 'center', justifyContent: 'center',
234
+ fontSize: 10, fontWeight: 800, color: inkOn(p.ink || '#888888'), overflow: 'hidden', whiteSpace: 'nowrap',
235
+ }}>{p.label && (100 * p.value) / total >= 5 ? p.label : ''}</span>
236
+ ))}
237
+ </span>
238
+ );
239
+ }
@@ -19,7 +19,7 @@ import Link from 'next/link';
19
19
  import { Section, Body, LINE } from '../ui';
20
20
  import {
21
21
  AUTO, PEOPLE_PER_SEARCH, DANGER_AT, REPORTED_MONTH, LEVELS, usageLevel, warningParts, estimate,
22
- barMax, pacificText, whenText, untilText, agoText, hoursText,
22
+ barMax, pacificText, whenText, untilText, agoText, hoursText, INVITE_CAPS, REPORTED_WEEKLY_INVITES,
23
23
  } from '../../../lib/usage';
24
24
  import { RESTRICTED_AT, RISKY_DAILY, SAFE_LIMITS } from '../../../lib/search-risk';
25
25
  import { paceOf, searchesPerHour } from '../../../lib/scan-pace';
@@ -100,6 +100,51 @@ function Meter({ title, used, of, max, color, ticks = [], band, children }) {
100
100
  );
101
101
  }
102
102
 
103
+ /**
104
+ * Auto's connection requests (lib/auto-connect.js): the last 24 hours and the
105
+ * last 7 days, each against its cap, one bar under the other. Its own caps,
106
+ * not the search budget: a request is the one thing Six Degrees sends.
107
+ */
108
+ function InviteMeter({ day, week, freesAt, weekFreesAt, now }) {
109
+ const known = Number.isFinite(day) && Number.isFinite(week);
110
+ const bars = [
111
+ { label: 'Last 24 hours', used: day, cap: INVITE_CAPS.day },
112
+ { label: 'Last 7 days', used: week, cap: INVITE_CAPS.week },
113
+ ];
114
+ const left = known ? Math.max(0, Math.min(INVITE_CAPS.day - day, INVITE_CAPS.week - week)) : null;
115
+ return (
116
+ <div style={{ marginTop: 18 }} data-usage="invites">
117
+ <div style={{ fontSize: 13.5, fontWeight: 650, color: 'var(--sd-fg-1, #e8e8ee)' }}>Connection requests (Auto)</div>
118
+ {bars.map((b) => (
119
+ <div key={b.label} style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 8 }}>
120
+ <span style={{ flex: '0 0 88px', fontSize: 12, color: 'var(--sd-fg-3, #8b9a9a)' }}>{b.label}</span>
121
+ <div style={{ position: 'relative', flex: 1, height: 10, borderRadius: 5, background: 'rgba(var(--sd-ink, 255, 255, 255), 0.07)' }}>
122
+ <div style={{
123
+ position: 'absolute', top: 0, bottom: 0, left: 0, borderRadius: 5, transition: 'width 0.4s ease',
124
+ width: Number.isFinite(b.used) ? at(b.used, Math.max(b.cap, b.used)) : 0,
125
+ background: b.used >= b.cap ? '#ff6b6b' : 'linear-gradient(90deg, #FFD700, #FF6B35)',
126
+ }} />
127
+ </div>
128
+ <span style={{ flex: '0 0 auto', fontSize: 13, color: 'var(--sd-fg-3, #8b9a9a)', fontVariantNumeric: 'tabular-nums', minWidth: 58, textAlign: 'right' }}>
129
+ <b style={{ fontSize: 15, color: 'var(--sd-fg-1, #fff)' }}>{count(b.used)}</b> of {b.cap}
130
+ </span>
131
+ </div>
132
+ ))}
133
+ <div style={{ fontSize: 12.5, color: 'var(--sd-fg-3, #8b9a9a)', lineHeight: 1.6, marginTop: 6 }}>
134
+ {!known ? null
135
+ : freesAt ? <>Auto&rsquo;s {INVITE_CAPS.day} a day are used. The next one frees {whenAndUntil(freesAt, now)}.</>
136
+ : weekFreesAt ? <>Auto&rsquo;s {INVITE_CAPS.week} a week are used. The next one frees {whenAndUntil(weekFreesAt, now)}.</>
137
+ : <>{plural(left, 'request')} left for Auto now.</>}
138
+ <div style={small}>
139
+ Each press of Auto sends one request from the scanner&rsquo;s Chrome, without a note, and opens their
140
+ profile once (a profile view). It stops at {INVITE_CAPS.day} in any 24 hours and {INVITE_CAPS.week} in any 7 days.
141
+ LinkedIn doesn&rsquo;t publish its invitation limit; people commonly report about {REPORTED_WEEKLY_INVITES} a week.
142
+ </div>
143
+ </div>
144
+ </div>
145
+ );
146
+ }
147
+
103
148
  function Row({ label, children }) {
104
149
  return (
105
150
  <div style={{ display: 'flex', gap: '4px 16px', flexWrap: 'wrap', padding: '10px 0', borderTop: LINE, fontSize: 13 }}>
@@ -260,6 +305,8 @@ export default function UsageSection() {
260
305
  </div>
261
306
  </Meter>
262
307
 
308
+ <InviteMeter day={u.invitesToday} week={u.invitesWeek} freesAt={u.invitesFreeAt} weekFreesAt={u.invitesWeekFreeAt} now={now} />
309
+
263
310
  <div style={{ marginTop: 22 }}>
264
311
  <Row label="Speed">
265
312
  {pace.label}: about {searchesPerHour(limits.pace)} searches an hour at most while a scan runs.
@@ -301,8 +348,8 @@ export default function UsageSection() {
301
348
  </div>
302
349
 
303
350
  <div style={{ ...small, marginTop: 14, paddingTop: 12, borderTop: LINE }}>
304
- Where these numbers come from: Six Degrees writes down every search and profile view it makes on your
305
- LinkedIn account, on this computer, and counts them here. Searches you make yourself on linkedin.com
351
+ Where these numbers come from: Six Degrees writes down every search, profile view and Auto request it makes
352
+ on your LinkedIn account, on this computer, and counts them here. Searches you make yourself on linkedin.com
306
353
  aren&rsquo;t in it, and LinkedIn shows no count of its own. The {RESTRICTED_AT} searches and the 20 to 25
307
354
  profile views are what happened to one real account, not safe limits. Change the budget on the
308
355
  {' '}<Link href="/setup" style={link}>Scan page</Link>, under Fine-tune the scanner. Nothing here is sent anywhere.
@@ -107,13 +107,12 @@ html:has([data-field-question]) {
107
107
  border-radius: 3px;
108
108
  }
109
109
 
110
- /* Where the scan status bar sits: just under the header (app/components/ScanStatusBar.js). */
111
- :root { --scan-bar-top: 94px; }
112
- @media (max-width: 767px) { :root { --scan-bar-top: 66px; } }
113
110
 
114
111
  /* The notch's running dot (app/components/ScanStatusBar.js). */
115
112
  @keyframes notch-pulse { 50% { opacity: 0.35; } }
116
113
  @media (prefers-reduced-motion: no-preference) { .notch-pulse { animation: notch-pulse 1.6s ease-in-out infinite; } }
114
+ @keyframes notch-in { from { opacity: 0; } }
115
+ @media (prefers-reduced-motion: no-preference) { .notch-in { animation: notch-in 0.25s ease-out; } }
117
116
 
118
117
  /* The Social tab's CRM (app/social/Crm.js): the list and the person side by
119
118
  side, stacked on a phone. In Pipeline the board takes the whole width and
@@ -0,0 +1,220 @@
1
+ 'use client';
2
+
3
+ // Insights: your network ranked by power, and the boards that read it
4
+ // (Blake, 2026-10-03: "stats and insights … data and insights based off their
5
+ // circles … almost no moral tie to the person … base it off value like
6
+ // richest person or highest power people … an intro perspective look at the
7
+ // data of what we have"). Mock-up B, "The Power Index", is the page; mock-up
8
+ // C, the scrolling report, is its Report view.
9
+ //
10
+ // Its views sit in the notch under the header, as every page's do
11
+ // (lib/island.js setNotchTabs): People, Kingmakers, Gatekeepers, Companies,
12
+ // Industries and Report. Every number comes from lib/insights-board.js, which
13
+ // says where each came from; nothing here is estimated and nothing is about
14
+ // money.
15
+
16
+ import { Suspense, useCallback, useEffect, useMemo, useState } from 'react';
17
+ import { useSearchParams } from 'next/navigation';
18
+ import Link from 'next/link';
19
+ import AppHeader from '../components/AppHeader';
20
+ import OnboardingGate from '../components/OnboardingGate';
21
+ import { useUser } from '../components/UserProvider';
22
+ import useRequests from '../components/useRequests';
23
+ import { setNotchTabs } from '../../lib/island';
24
+ import { loadNetwork } from '../../lib/network';
25
+ import { loadCsvNetwork } from '../../lib/csv';
26
+ import { loadScanNotes, NO_SCAN_NOTES } from '../../lib/scraper-client';
27
+ import { hasRequest } from '../../lib/requests-client';
28
+ import { networkLevel } from '../../lib/level';
29
+ import { IS_DEMO } from '../../lib/demo';
30
+ import {
31
+ boardBase, powerIndex, kingmakers, gatekeepers, hiddenGiants, tierByRarity, untapped,
32
+ companyPower, industries, titleLadder, coverage, howSure, reachSummary,
33
+ } from '../../lib/insights-board';
34
+ import PeopleView from '../components/insights/PeopleView';
35
+ import { KingmakersBoard, GatekeepersBoard, CompaniesBoard, IndustriesBoard } from '../components/insights/Boards';
36
+ import Report from '../components/insights/Report';
37
+ import { fmt, useIsMobile, TITLE } from '../components/insights/parts';
38
+
39
+ const VIEWS = ['people', 'kingmakers', 'gatekeepers', 'companies', 'industries', 'report'];
40
+ const LABELS = { people: 'People', kingmakers: 'Kingmakers', gatekeepers: 'Gatekeepers', companies: 'Companies', industries: 'Industries', report: 'Report' };
41
+
42
+ export default function InsightsPage() {
43
+ // Suspense because the page reads the address's ?view= (useSearchParams),
44
+ // which Next requires to sit inside one for the page to build (TRAPS §41).
45
+ return <OnboardingGate><Suspense><InsightsInner /></Suspense></OnboardingGate>;
46
+ }
47
+
48
+ function InsightsInner() {
49
+ const { userId } = useUser();
50
+ const isMobile = useIsMobile();
51
+ const asked = useSearchParams().get('view');
52
+ const [view, setView] = useState(() => (VIEWS.includes(asked) ? asked : 'people'));
53
+ const [net, setNet] = useState(null); // { degree1, degree2, degree3, notes, source, budget }
54
+ const [failed, setFailed] = useState(null);
55
+
56
+ useEffect(() => {
57
+ if (IS_DEMO || !userId) return;
58
+ let live = true;
59
+ (async () => {
60
+ try {
61
+ // The sample (this window) or a kept CSV import, else your own network:
62
+ // lib/csv.js openNetworkSource, the one rule every page opens on.
63
+ const local = await loadCsvNetwork();
64
+ if (local) {
65
+ if (live) setNet({ degree1: local.degree1 || [], degree2: local.degree2 || [], degree3: [], notes: NO_SCAN_NOTES, source: local.source || 'csv', budget: null });
66
+ return;
67
+ }
68
+ // Your daily search budget and speed, for what scanning the next circles would take:
69
+ // ?usage only reads the scanner's files (no checks, no Python, nothing written).
70
+ const usage = fetch('/api/scraper?usage').then((r) => (r.ok ? r.json() : null)).catch(() => null);
71
+ const [n, notes, used] = await Promise.all([loadNetwork(userId), loadScanNotes(), usage]);
72
+ const limits = used?.limits;
73
+ if (live) setNet({ ...n, notes, source: 'own', budget: limits ? { daily: limits.daily, pace: limits.pace } : null });
74
+ } catch (e) {
75
+ if (live) setFailed(e.message || 'The network couldn’t be loaded.');
76
+ }
77
+ })();
78
+ return () => { live = false; };
79
+ }, [userId]);
80
+
81
+ // The quick part first: the ranking and the boards that read circles.
82
+ const base = useMemo(() => (net ? boardBase(net) : null), [net]);
83
+ const data = useMemo(() => {
84
+ if (!base) return null;
85
+ const index = powerIndex(base);
86
+ return {
87
+ index, reach: reachSummary(base), kings: kingmakers(base), gates: gatekeepers(base), hidden: hiddenGiants(base),
88
+ grid: tierByRarity(base), cover: coverage(base, net.budget || {}), sure: howSure(index),
89
+ };
90
+ }, [base, net]);
91
+ // Companies and titles read every headline (about half a second on 7,000
92
+ // people), so they follow once the table is on screen.
93
+ const [slow, setSlow] = useState(null);
94
+ useEffect(() => {
95
+ if (!base) return undefined;
96
+ const t = setTimeout(() => {
97
+ const companies = companyPower(base);
98
+ setSlow({ base, companies, industries: industries(companies), ladder: titleLadder(base) });
99
+ }, 30);
100
+ return () => clearTimeout(t);
101
+ }, [base]);
102
+ const heavy = slow && slow.base === base ? slow : null;
103
+
104
+ const requests = useRequests();
105
+ const isAsked = useCallback((row) => hasRequest(row, requests), [requests]);
106
+ const open = useMemo(() => (base ? untapped(base, isAsked) : null), [base, isAsked]);
107
+
108
+ const pick = useCallback((v) => {
109
+ setView(v);
110
+ try {
111
+ const u = new URL(window.location.href);
112
+ if (v === 'people') u.searchParams.delete('view'); else u.searchParams.set('view', v);
113
+ window.history.replaceState(null, '', u.pathname + u.search);
114
+ } catch { /* the address stays */ }
115
+ window.scrollTo(0, 0);
116
+ }, []);
117
+
118
+ // The views in the notch, with how many each ranks.
119
+ useEffect(() => {
120
+ if (!data || data.index.total === 0) { setNotchTabs(null); return undefined; }
121
+ const count = {
122
+ people: data.index.total, kingmakers: data.kings.scanned, gatekeepers: data.gates.list.length,
123
+ companies: heavy?.companies.count, industries: heavy?.industries.list.length,
124
+ };
125
+ setNotchTabs({
126
+ items: VIEWS.map((key) => ({
127
+ key,
128
+ label: count[key] ? <>{LABELS[key]}<small style={{ fontSize: 10.5, opacity: 0.65, fontWeight: 600 }}>{fmt(count[key])}</small></> : LABELS[key],
129
+ })),
130
+ current: view, onPick: pick,
131
+ });
132
+ return () => setNotchTabs(null);
133
+ }, [data, heavy, view, pick]);
134
+
135
+ const level = useMemo(() => (net ? networkLevel(net.degree1, net.degree2) : null), [net]);
136
+ const csvMode = Boolean(net && net.source !== 'own');
137
+
138
+ if (IS_DEMO) {
139
+ return (
140
+ <div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 12, color: 'var(--sd-fg-2, #ccc)', fontFamily: 'var(--sd-font)' }}>
141
+ <h1 style={{ fontSize: 20, fontWeight: 700, margin: 0, color: 'var(--sd-fg-1, #fff)' }}>Not part of the demo</h1>
142
+ <p style={{ fontSize: 13, margin: 0 }}>The public demo includes the Network Circle and Degrees views only.</p>
143
+ <Link href="/" style={{ fontSize: 13, color: 'var(--sd-fg-3, #888)', textDecoration: 'none' }}>&larr; Back to the network</Link>
144
+ </div>
145
+ );
146
+ }
147
+
148
+ const shared = { data, heavy, open, net, source: net?.source, isMobile, onView: pick };
149
+ return (
150
+ <div style={{ minHeight: '100vh', background: 'var(--sd-page)', color: 'var(--sd-fg-1, #fff)', fontFamily: 'var(--sd-font)' }}>
151
+ <AppHeader active="insights" csvMode={csvMode} level={level} isMobile={isMobile} />
152
+ {/* Room for the notch, which hangs under the header with the views in it */}
153
+ <main style={{ maxWidth: view === 'report' ? 920 : 1400, margin: '0 auto', padding: isMobile ? '52px 12px 0' : '58px 30px 0' }}>
154
+ {failed ? <Message title="The network couldn’t be loaded" body={failed} />
155
+ : !data ? <Message title="Reading your network…" />
156
+ : data.index.total === 0 ? <Empty source={net.source} />
157
+ : view === 'people' ? <PeopleView {...shared} requests={requests} />
158
+ : view === 'kingmakers' ? <KingmakersBoard {...shared} />
159
+ : view === 'gatekeepers' ? <GatekeepersBoard {...shared} />
160
+ : view === 'companies' ? <CompaniesBoard {...shared} />
161
+ : view === 'industries' ? <IndustriesBoard {...shared} />
162
+ : <Report {...shared} />}
163
+ <Footer net={net} data={data} isMobile={isMobile} />
164
+ </main>
165
+ </div>
166
+ );
167
+ }
168
+
169
+ function Message({ title, body }) {
170
+ return (
171
+ <div style={{ padding: '80px 0', textAlign: 'center' }}>
172
+ <div style={{ fontSize: 17, fontWeight: 700 }}>{title}</div>
173
+ {body && <div style={{ fontSize: 13, color: 'var(--sd-fg-3, #888)', marginTop: 8 }}>{body}</div>}
174
+ </div>
175
+ );
176
+ }
177
+
178
+ /** No one to rank yet: where people come from. */
179
+ function Empty({ source }) {
180
+ return (
181
+ <div style={{ maxWidth: 560, margin: '60px auto', textAlign: 'center' }}>
182
+ <h1 style={TITLE}>The Power Index</h1>
183
+ <p style={{ fontSize: 15, color: 'var(--sd-fg-2, #b8c4c4)', lineHeight: 1.6, margin: '12px 0 22px' }}>
184
+ Nothing to rank yet. Once your connections are in, everyone within two steps of you shows here, ranked by power,
185
+ with who holds the doors and what’s still closed.
186
+ </p>
187
+ <div style={{ display: 'flex', gap: 10, justifyContent: 'center', flexWrap: 'wrap' }}>
188
+ {source === 'own' && (
189
+ <Link href="/setup" style={{ padding: '10px 16px', borderRadius: 8, fontWeight: 700, fontSize: 13.5, textDecoration: 'none', color: '#000', background: 'linear-gradient(135deg, #00ff88, #1abc9c)' }}>
190
+ Scan your connections →
191
+ </Link>
192
+ )}
193
+ <Link href="/import" style={{ padding: '10px 16px', borderRadius: 8, fontWeight: 700, fontSize: 13.5, textDecoration: 'none', color: 'var(--sd-fg-1, #fff)', border: '1px solid rgba(var(--sd-ink, 255, 255, 255), 0.2)' }}>
194
+ Import a CSV
195
+ </Link>
196
+ </div>
197
+ <p style={{ fontSize: 12.5, color: 'var(--sd-fg-4, #6f7a88)', marginTop: 18 }}>
198
+ Or look around first: the <Link href="/" style={{ color: 'var(--sd-blue, #3498DB)', textDecoration: 'none' }}>map’s welcome screen</Link> opens a sample network where every person is invented.
199
+ </p>
200
+ </div>
201
+ );
202
+ }
203
+
204
+ /** The rule every screen with a tier says, and which network this is. */
205
+ function Footer({ net, data, isMobile }) {
206
+ const what = !net || !data?.index.total ? null
207
+ : net.source === 'sample' ? 'The sample network: every person in it is invented.'
208
+ : net.source === 'csv' ? `Your CSV import: ${fmt(data?.reach.d1)} connections, no circles.`
209
+ : data ? `Counted from your ${fmt(data.reach.d1)} connections and the ${fmt(data.reach.circles)} circle${data.reach.circles === 1 ? '' : 's'} you’ve scanned.` : null;
210
+ return (
211
+ <footer style={{
212
+ margin: '36px 0 0', padding: '18px 0 34px', borderTop: '1px solid rgba(var(--sd-ink, 255, 255, 255), 0.08)',
213
+ display: 'flex', flexDirection: isMobile ? 'column' : 'row', justifyContent: 'space-between', gap: isMobile ? 8 : 20,
214
+ color: 'var(--sd-fg-4, #6f7a88)', fontSize: 11.5, lineHeight: 1.5,
215
+ }}>
216
+ <div><b style={{ color: 'var(--sd-fg-2, #b8c4c4)' }}>Power ranks reachability, not people.</b> A tier is a statement about network position, not human worth.</div>
217
+ {what && <div>{what}</div>}
218
+ </footer>
219
+ );
220
+ }
@@ -4,8 +4,6 @@ import { Suspense, useEffect, useState, useRef, useMemo, useCallback } from 'rea
4
4
  import { useSearchParams } from 'next/navigation';
5
5
  import { fillsCircles, loadScanNotes, NO_SCAN_NOTES } from '../lib/scraper-client';
6
6
  import useScanner from './components/useScanner';
7
- import useRequests from './components/useRequests';
8
- import { requestCount } from '../lib/requests-client';
9
7
  import { loadNetwork } from '../lib/network';
10
8
  import { resolveView } from './components/views';
11
9
  import { peopleByDegree } from '../lib/degrees';
@@ -105,7 +103,6 @@ function HomeInner() {
105
103
  const [scanNotes, setScanNotes] = useState(NO_SCAN_NOTES);
106
104
  const shapeRef = useRef('');
107
105
  // Everyone with a request out, shared with every view (lib/requests-client.js).
108
- const pendingCount = requestCount(useRequests());
109
106
 
110
107
  useEffect(() => {
111
108
  if (!userId) return;
@@ -305,20 +302,7 @@ function HomeInner() {
305
302
  </button>
306
303
  </div>
307
304
  )}
308
- >
309
- {/* Pending count badge — in header: everyone with a request out, once each (lib/requests-client.js) */}
310
- {isDegreesMode && !csvMode && pendingCount > 0 && (
311
- <Link href="/queue" style={{
312
- padding: '5px 12px', borderRadius: 16, fontSize: 12, fontWeight: 600, textDecoration: 'none',
313
- background: 'rgba(255,107,53,0.12)', color: 'var(--sd-orange, #FF6B35)',
314
- border: '1px solid rgba(255,107,53,0.25)',
315
- display: 'inline-flex', alignItems: 'center', gap: 5,
316
- }}>
317
- <span style={{ fontSize: 10 }}>⏳</span>
318
- {pendingCount} Pending
319
- </Link>
320
- )}
321
- </AppHeader>
305
+ />
322
306
 
323
307
  <div style={{ display: 'flex', height: isMobile ? 'calc(100vh - 70px)' : 'calc(100vh - 130px)', position: 'relative' }}>
324
308
  <FilterPanel