six-degrees 0.3.0 → 0.4.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 (427) 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 +35 -7
  5. package/.next/standalone/.next/required-server-files.json +6 -1
  6. package/.next/standalone/.next/routes-manifest.json +18 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.rsc +11 -11
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +11 -11
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +10 -10
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  21. package/.next/standalone/.next/server/app/api/admin-delete/route.js +1 -1
  22. package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/admin-update/route.js +1 -1
  24. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/bridges/route.js +1 -1
  26. package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/bulk-import/route.js +1 -1
  28. package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/connections/route.js +1 -1
  32. package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/data/restart/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 +1 -1
  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/unlock/route.js +1 -1
  58. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  60. package/.next/standalone/.next/server/app/api/update-images/route.js +1 -1
  61. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/api/users/route.js +1 -1
  63. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  64. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/import.html +1 -1
  68. package/.next/standalone/.next/server/app/import.rsc +13 -13
  69. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +13 -13
  70. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
  71. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +12 -12
  72. package/.next/standalone/.next/server/app/index.html +1 -1
  73. package/.next/standalone/.next/server/app/index.rsc +13 -13
  74. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +12 -12
  75. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +13 -13
  76. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  77. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  78. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  79. package/.next/standalone/.next/server/app/launch.html +2 -2
  80. package/.next/standalone/.next/server/app/launch.rsc +13 -13
  81. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +13 -13
  82. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
  83. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +12 -12
  84. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  85. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  86. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  87. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  88. package/.next/standalone/.next/server/app/paths.html +1 -1
  89. package/.next/standalone/.next/server/app/paths.rsc +13 -13
  90. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +13 -13
  91. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
  92. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +12 -12
  93. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  94. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  95. package/.next/standalone/.next/server/app/profile.html +1 -1
  96. package/.next/standalone/.next/server/app/profile.rsc +13 -13
  97. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +13 -13
  98. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
  99. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +12 -12
  100. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  101. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  102. package/.next/standalone/.next/server/app/queue.html +1 -1
  103. package/.next/standalone/.next/server/app/queue.rsc +13 -13
  104. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +13 -13
  105. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
  106. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +12 -12
  107. package/.next/standalone/.next/server/app/scores/page/app-paths-manifest.json +3 -0
  108. package/.next/standalone/.next/server/app/scores/page/build-manifest.json +20 -0
  109. package/.next/standalone/.next/server/app/scores/page/next-font-manifest.json +11 -0
  110. package/.next/standalone/.next/server/app/scores/page/react-loadable-manifest.json +1 -0
  111. package/.next/standalone/.next/server/app/scores/page/server-reference-manifest.json +4 -0
  112. package/.next/standalone/.next/server/app/scores/page.js +13 -0
  113. package/.next/standalone/.next/server/app/scores/page.js.map +5 -0
  114. package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -0
  115. package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +3 -0
  116. package/.next/standalone/.next/server/app/scores.html +1 -0
  117. package/.next/standalone/.next/server/app/scores.meta +26 -0
  118. package/.next/standalone/.next/server/app/scores.rsc +24 -0
  119. package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +24 -0
  120. package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +4 -0
  121. package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +28 -0
  122. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  123. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  124. package/.next/standalone/.next/server/app/settings.html +1 -1
  125. package/.next/standalone/.next/server/app/settings.rsc +13 -13
  126. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +13 -13
  127. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  128. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +12 -12
  129. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  130. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  131. package/.next/standalone/.next/server/app/setup.html +1 -1
  132. package/.next/standalone/.next/server/app/setup.rsc +13 -13
  133. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +13 -13
  134. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
  135. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +12 -12
  136. package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
  137. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +1 -1
  138. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +1 -1
  139. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0i5hdu4._.js → [root-of-the-server]__03ydxxh._.js} +2 -2
  140. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +1 -1
  141. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0bat8nx._.js +8 -0
  142. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +1 -1
  143. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +1 -1
  144. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljcioj._.js +1 -1
  145. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +1 -1
  147. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +1 -1
  148. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
  149. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uag-k5._.js +1 -1
  150. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0um1uey._.js +1 -1
  151. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +1 -1
  152. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +1 -1
  153. package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +1 -1
  154. package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +1 -1
  155. package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +1 -1
  156. package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +1 -1
  157. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +1 -1
  158. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +2 -2
  159. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +1 -1
  160. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +1 -1
  161. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
  162. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +1 -1
  163. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +1 -1
  164. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +1 -1
  165. package/.next/standalone/.next/server/chunks/_02twlca._.js +10 -0
  166. package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
  167. package/.next/standalone/.next/server/chunks/_0lw8_i0._.js +1 -1
  168. package/.next/standalone/.next/server/chunks/_1_fynia._.js +8 -6
  169. package/.next/standalone/.next/server/chunks/_1fn30_l._.js +1 -1
  170. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
  171. package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
  172. package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +13 -8
  173. package/.next/standalone/.next/server/chunks/{lib_rpc_02ndsg2.js → lib_rpc_03i8xef.js} +1 -1
  174. package/.next/standalone/.next/server/chunks/{lib_rpc_08q5vp6.js → lib_rpc_08fc3rp.js} +1 -1
  175. package/.next/standalone/.next/server/chunks/{lib_rpc_00tx2sm.js → lib_rpc_08r2t_q.js} +1 -1
  176. package/.next/standalone/.next/server/chunks/{lib_rpc_02-qa4d.js → lib_rpc_0btpv1t.js} +1 -1
  177. package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +5 -5
  178. package/.next/standalone/.next/server/chunks/lib_rpc_0cm2ev3.js +3 -0
  179. package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +3 -0
  180. package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +3 -0
  181. package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +3 -0
  182. package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +3 -0
  183. package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +3 -0
  184. package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +3 -0
  185. package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +3 -0
  186. package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +3 -0
  187. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0an6blh._.js → [root-of-the-server]__0zzcbqg._.js} +2 -2
  188. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1194jps._.js +3 -0
  189. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1z1j3ek._.js → [root-of-the-server]__19ip1qq._.js} +2 -2
  190. package/.next/standalone/.next/server/chunks/ssr/_01f38ty._.js +3 -0
  191. package/.next/standalone/.next/server/chunks/ssr/_06w_gl4._.js +3 -0
  192. package/.next/standalone/.next/server/chunks/ssr/_0hw_qnj._.js +3 -0
  193. package/.next/standalone/.next/server/chunks/ssr/_13kzrwv._.js +3 -0
  194. package/.next/standalone/.next/server/chunks/ssr/_15xuwkv._.js +3 -0
  195. package/.next/standalone/.next/server/chunks/ssr/_1cyxocj._.js +3 -0
  196. package/.next/standalone/.next/server/chunks/ssr/_1gpbbw9._.js +3 -0
  197. package/.next/standalone/.next/server/chunks/ssr/_1hgd5dp._.js +3 -0
  198. package/.next/standalone/.next/server/chunks/ssr/_1vpfyd-._.js +3 -0
  199. package/.next/standalone/.next/server/chunks/ssr/_20w06i-._.js +3 -0
  200. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_scores_page_actions_0x-w58n.js +3 -0
  201. package/.next/standalone/.next/server/chunks/ssr/app_components_settings_SectorPicker_1c-29qw.js +3 -0
  202. package/.next/standalone/.next/server/chunks/ssr/app_launch_page_19t0dyd.js +1 -1
  203. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +11 -9
  204. package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +4 -0
  205. package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
  206. package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +5 -5
  207. package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +3 -0
  208. package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
  209. package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +2 -2
  210. package/.next/standalone/.next/server/chunks/ssr/lib_1bn38gs._.js +3 -0
  211. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0x3i8za._.js +131 -0
  212. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_1bunoni._.js → node_modules_next_dist_1v8aef8._.js} +2 -2
  213. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_client_components_server-async-storage_0t1dato.js +3 -0
  214. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  215. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  216. package/.next/standalone/.next/server/next-font-manifest.js +1 -1
  217. package/.next/standalone/.next/server/next-font-manifest.json +4 -0
  218. package/.next/standalone/.next/server/pages/404.html +1 -1
  219. package/.next/standalone/.next/server/pages/500.html +1 -1
  220. package/.next/standalone/.next/server/prefetch-hints.json +14 -0
  221. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  222. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  223. package/.next/standalone/.next/static/chunks/0ax_9zzboyv6d.js +44 -0
  224. package/.next/standalone/.next/static/chunks/0cuuo2jwc9h94.js +1 -0
  225. package/.next/standalone/.next/static/chunks/0l1568qff0efj.js +1 -0
  226. package/.next/standalone/.next/static/chunks/0muwt9bykzr-1.js +1 -0
  227. package/.next/standalone/.next/static/chunks/0w76iraw7ij3k.js +18 -0
  228. package/.next/standalone/.next/static/chunks/135n87ydxjrra.js +2 -0
  229. package/.next/standalone/.next/static/chunks/18xu0fbgo_qz-.js +1 -0
  230. package/.next/standalone/.next/static/chunks/28bu26ausqzmn.js +1 -0
  231. package/.next/standalone/.next/static/chunks/{40nvbgfn7-072.css → 2p6o90z0vgcwr.css} +1 -1
  232. package/.next/standalone/.next/static/chunks/2pnbm1z6j7xab.js +1 -0
  233. package/.next/standalone/.next/static/chunks/2xjho0lroiwlq.js +1 -0
  234. package/.next/standalone/.next/static/chunks/3e8zg6ctvvb8p.js +33 -0
  235. package/.next/standalone/.next/static/chunks/3qh78g2aqhprd.js +2 -0
  236. package/.next/standalone/CHANGELOG.md +286 -0
  237. package/.next/standalone/CONTRIBUTING.md +15 -4
  238. package/.next/standalone/README.md +50 -29
  239. package/.next/standalone/SECURITY.md +10 -0
  240. package/.next/standalone/app/api/company-scores/legacy/route.js +1 -1
  241. package/.next/standalone/app/api/company-scores/route.js +1 -1
  242. package/.next/standalone/app/api/ingest/route.js +23 -2
  243. package/.next/standalone/app/api/outreach/route.js +107 -44
  244. package/.next/standalone/app/api/scraper/route.js +146 -17
  245. package/.next/standalone/app/api/settings/sector-preview/route.js +3 -2
  246. package/.next/standalone/app/api/settings/sector-suggestions/route.js +1 -1
  247. package/.next/standalone/app/api/update-images/route.js +41 -7
  248. package/.next/standalone/app/avatars/[file]/route.js +6 -1
  249. package/.next/standalone/app/components/Avatar.js +5 -2
  250. package/.next/standalone/app/components/ChainView.js +659 -469
  251. package/.next/standalone/app/components/CompanyScores.js +16 -10
  252. package/.next/standalone/app/components/EmptyState.js +1 -1
  253. package/.next/standalone/app/components/FieldStep.js +94 -0
  254. package/.next/standalone/app/components/ForceGraph.js +183 -768
  255. package/.next/standalone/app/components/GridView.js +9 -7
  256. package/.next/standalone/app/components/LegacyScoresCard.js +1 -1
  257. package/.next/standalone/app/components/LinkedInLimits.js +22 -7
  258. package/.next/standalone/app/components/ListView.js +6 -4
  259. package/.next/standalone/app/components/MappingProgress.js +5 -6
  260. package/.next/standalone/app/components/OnboardingGate.js +1 -1
  261. package/.next/standalone/app/components/OrbitGraph.js +33 -1
  262. package/.next/standalone/app/components/OutlinkQuest.js +5 -4
  263. package/.next/standalone/app/components/PathsAnalyzer.js +1 -1
  264. package/.next/standalone/app/components/RingsView.js +9 -8
  265. package/.next/standalone/app/components/SeparationView.js +96 -14
  266. package/.next/standalone/app/components/Sidebar.js +366 -287
  267. package/.next/standalone/app/components/TheirCircle.js +300 -0
  268. package/.next/standalone/app/components/settings/SectorPicker.js +202 -0
  269. package/.next/standalone/app/components/settings/SectorSection.js +21 -195
  270. package/.next/standalone/app/components/settings/TierSection.js +99 -0
  271. package/.next/standalone/app/components/useRequests.js +12 -0
  272. package/.next/standalone/app/components/useScanner.js +13 -0
  273. package/.next/standalone/app/components/views.js +3 -5
  274. package/.next/standalone/app/globals.css +7 -0
  275. package/.next/standalone/app/launch/page.js +6 -3
  276. package/.next/standalone/app/page.js +183 -74
  277. package/.next/standalone/app/paths/page.js +20 -14
  278. package/.next/standalone/app/profile/page.js +3 -3
  279. package/.next/standalone/app/queue/page.js +56 -60
  280. package/.next/standalone/app/scores/page.js +72 -0
  281. package/.next/standalone/app/settings/page.js +15 -3
  282. package/.next/standalone/app/setup/page.js +423 -260
  283. package/.next/standalone/db/schema.js +2 -1
  284. package/.next/standalone/eslint.config.mjs +3 -3
  285. package/.next/standalone/install.sh +3 -3
  286. package/.next/standalone/lib/chain-layout.js +139 -0
  287. package/.next/standalone/lib/circle.js +169 -0
  288. package/.next/standalone/lib/companies.js +1 -1
  289. package/.next/standalone/lib/csv.js +21 -10
  290. package/.next/standalone/lib/data-export.js +2 -1
  291. package/.next/standalone/lib/data-folder.js +4 -2
  292. package/.next/standalone/lib/db-client.js +1 -0
  293. package/.next/standalone/lib/galaxy.js +12 -0
  294. package/.next/standalone/lib/ingest.js +27 -8
  295. package/.next/standalone/lib/legacy-offer.js +7 -6
  296. package/.next/standalone/lib/legacy-scores.js +2 -2
  297. package/.next/standalone/lib/linkedin-limits.js +30 -8
  298. package/.next/standalone/lib/photos.js +47 -0
  299. package/.next/standalone/lib/quest.js +8 -3
  300. package/.next/standalone/lib/rarity.js +80 -0
  301. package/.next/standalone/lib/reach.js +113 -0
  302. package/.next/standalone/lib/requests-client.js +113 -0
  303. package/.next/standalone/lib/rpc.js +26 -15
  304. package/.next/standalone/lib/scan-state.js +1 -0
  305. package/.next/standalone/lib/scanner-setup.js +20 -1
  306. package/.next/standalone/lib/score-guess.js +60 -0
  307. package/.next/standalone/lib/scoring.js +1761 -69
  308. package/.next/standalone/lib/scraper-client.js +235 -17
  309. package/.next/standalone/lib/sector-directory.js +1 -1
  310. package/.next/standalone/lib/sector-focus.js +17 -3
  311. package/.next/standalone/lib/separation.js +4 -2
  312. package/.next/standalone/lib/settings-client.js +21 -0
  313. package/.next/standalone/lib/settings-effects.js +19 -1
  314. package/.next/standalone/lib/settings.js +7 -2
  315. package/.next/standalone/next.config.mjs +25 -3
  316. package/.next/standalone/package-lock.json +2 -2
  317. package/.next/standalone/package.json +2 -2
  318. package/.next/standalone/public/demo-data.json +1 -1
  319. package/.next/standalone/scripts/build-app.mjs +11 -2
  320. package/.next/standalone/scripts/check-node.mjs +1 -1
  321. package/.next/standalone/scripts/gen-synthetic.mjs +1 -1
  322. package/.next/standalone/scripts/image_store.py +77 -21
  323. package/.next/standalone/scripts/scrape.py +305 -29
  324. package/.next/standalone/server.js +1 -1
  325. package/.next/static/chunks/0ax_9zzboyv6d.js +44 -0
  326. package/.next/static/chunks/0cuuo2jwc9h94.js +1 -0
  327. package/.next/static/chunks/0l1568qff0efj.js +1 -0
  328. package/.next/static/chunks/0muwt9bykzr-1.js +1 -0
  329. package/.next/static/chunks/0w76iraw7ij3k.js +18 -0
  330. package/.next/static/chunks/135n87ydxjrra.js +2 -0
  331. package/.next/static/chunks/18xu0fbgo_qz-.js +1 -0
  332. package/.next/static/chunks/28bu26ausqzmn.js +1 -0
  333. package/.next/static/chunks/{40nvbgfn7-072.css → 2p6o90z0vgcwr.css} +1 -1
  334. package/.next/static/chunks/2pnbm1z6j7xab.js +1 -0
  335. package/.next/static/chunks/2xjho0lroiwlq.js +1 -0
  336. package/.next/static/chunks/3e8zg6ctvvb8p.js +33 -0
  337. package/.next/static/chunks/3qh78g2aqhprd.js +2 -0
  338. package/CHANGELOG.md +286 -0
  339. package/README.md +50 -29
  340. package/SECURITY.md +10 -0
  341. package/db/schema.js +2 -1
  342. package/lib/chain-layout.js +139 -0
  343. package/lib/circle.js +169 -0
  344. package/lib/companies.js +1 -1
  345. package/lib/csv.js +21 -10
  346. package/lib/data-export.js +2 -1
  347. package/lib/data-folder.js +4 -2
  348. package/lib/db-client.js +1 -0
  349. package/lib/galaxy.js +12 -0
  350. package/lib/ingest.js +27 -8
  351. package/lib/legacy-offer.js +7 -6
  352. package/lib/legacy-scores.js +2 -2
  353. package/lib/linkedin-limits.js +30 -8
  354. package/lib/photos.js +47 -0
  355. package/lib/quest.js +8 -3
  356. package/lib/rarity.js +80 -0
  357. package/lib/reach.js +113 -0
  358. package/lib/requests-client.js +113 -0
  359. package/lib/rpc.js +26 -15
  360. package/lib/scan-state.js +1 -0
  361. package/lib/scanner-setup.js +20 -1
  362. package/lib/score-guess.js +60 -0
  363. package/lib/scoring.js +1761 -69
  364. package/lib/scraper-client.js +235 -17
  365. package/lib/sector-directory.js +1 -1
  366. package/lib/sector-focus.js +17 -3
  367. package/lib/separation.js +4 -2
  368. package/lib/settings-client.js +21 -0
  369. package/lib/settings-effects.js +19 -1
  370. package/lib/settings.js +7 -2
  371. package/package.json +2 -2
  372. package/public/demo-data.json +1 -1
  373. package/scripts/check-node.mjs +1 -1
  374. package/scripts/image_store.py +77 -21
  375. package/scripts/scrape.py +305 -29
  376. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1t3rugi._.js +0 -3
  377. package/.next/standalone/.next/server/chunks/lib_rpc_0_8i3ft.js +0 -3
  378. package/.next/standalone/.next/server/chunks/lib_rpc_0huvw-3.js +0 -3
  379. package/.next/standalone/.next/server/chunks/lib_rpc_0ip2vdh.js +0 -3
  380. package/.next/standalone/.next/server/chunks/lib_rpc_0jzisgh.js +0 -3
  381. package/.next/standalone/.next/server/chunks/lib_rpc_0yi9xvd.js +0 -3
  382. package/.next/standalone/.next/server/chunks/lib_rpc_1jyhd0o.js +0 -3
  383. package/.next/standalone/.next/server/chunks/lib_rpc_1kn59-i.js +0 -3
  384. package/.next/standalone/.next/server/chunks/lib_rpc_1q2jkza.js +0 -3
  385. package/.next/standalone/.next/server/chunks/lib_rpc_1vpm1sk.js +0 -3
  386. package/.next/standalone/.next/server/chunks/lib_rpc_1x6p_31.js +0 -3
  387. package/.next/standalone/.next/server/chunks/ssr/_07u8byv._.js +0 -3
  388. package/.next/standalone/.next/server/chunks/ssr/_08r_mlf._.js +0 -3
  389. package/.next/standalone/.next/server/chunks/ssr/_0942v8n._.js +0 -3
  390. package/.next/standalone/.next/server/chunks/ssr/_0hl_t7r._.js +0 -3
  391. package/.next/standalone/.next/server/chunks/ssr/_0tb83dw._.js +0 -132
  392. package/.next/standalone/.next/server/chunks/ssr/_1-87bs1._.js +0 -3
  393. package/.next/standalone/.next/server/chunks/ssr/_17gui6a._.js +0 -3
  394. package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +0 -3
  395. package/.next/standalone/.next/server/chunks/ssr/node_modules_16vay6l._.js +0 -3
  396. package/.next/standalone/.next/server/chunks/ssr/node_modules_1kciwn4._.js +0 -3
  397. package/.next/standalone/.next/static/chunks/06x3jiiz96m5t.js +0 -1
  398. package/.next/standalone/.next/static/chunks/08k1crid-05dt.js +0 -1
  399. package/.next/standalone/.next/static/chunks/0ngx9s6-zgshe.js +0 -33
  400. package/.next/standalone/.next/static/chunks/0q-_khndfxubv.js +0 -2
  401. package/.next/standalone/.next/static/chunks/1c79veqedvubt.js +0 -1
  402. package/.next/standalone/.next/static/chunks/1chp3nx45meg5.js +0 -44
  403. package/.next/standalone/.next/static/chunks/1irqj0lf-fu3d.js +0 -1
  404. package/.next/standalone/.next/static/chunks/1uadqylrod5_v.js +0 -1
  405. package/.next/standalone/.next/static/chunks/23u0zclgwpc4d.js +0 -1
  406. package/.next/standalone/.next/static/chunks/289ktj97di6zg.js +0 -1
  407. package/.next/standalone/.next/static/chunks/2b3lmf8e6uj8m.js +0 -16
  408. package/.next/standalone/.next/static/chunks/3npq5yd7c4dmn.js +0 -2
  409. package/.next/standalone/app/components/BridgeRing.js +0 -753
  410. package/.next/static/chunks/06x3jiiz96m5t.js +0 -1
  411. package/.next/static/chunks/08k1crid-05dt.js +0 -1
  412. package/.next/static/chunks/0ngx9s6-zgshe.js +0 -33
  413. package/.next/static/chunks/0q-_khndfxubv.js +0 -2
  414. package/.next/static/chunks/1c79veqedvubt.js +0 -1
  415. package/.next/static/chunks/1chp3nx45meg5.js +0 -44
  416. package/.next/static/chunks/1irqj0lf-fu3d.js +0 -1
  417. package/.next/static/chunks/1uadqylrod5_v.js +0 -1
  418. package/.next/static/chunks/23u0zclgwpc4d.js +0 -1
  419. package/.next/static/chunks/289ktj97di6zg.js +0 -1
  420. package/.next/static/chunks/2b3lmf8e6uj8m.js +0 -16
  421. package/.next/static/chunks/3npq5yd7c4dmn.js +0 -2
  422. /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
  423. /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
  424. /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
  425. /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
  426. /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
  427. /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
@@ -1,5 +1,7 @@
1
1
  'use client';
2
2
 
3
+ import { localPhoto } from '../../lib/photos';
4
+
3
5
  const TIER_COLORS = { S: '#FFD700', A: '#9B59B6', B: '#3498DB', C: '#95A5A6', D: '#BDC3C7' };
4
6
  const TIERS = ['S', 'A', 'B', 'C', 'D'];
5
7
 
@@ -38,8 +40,8 @@ export default function GridView({ connections, degree2 = [], onSelect, mode })
38
40
  onMouseLeave={e => e.currentTarget.style.background = 'rgba(255,255,255,0.03)'}
39
41
  >
40
42
  <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
41
- {cl.bridge.profile_image_url ? (
42
- <img src={cl.bridge.profile_image_url} alt="" style={{
43
+ {localPhoto(cl.bridge.profile_image_url) ? (
44
+ <img src={localPhoto(cl.bridge.profile_image_url)} alt="" style={{
43
45
  width: 40, height: 40, borderRadius: '50%', objectFit: 'cover',
44
46
  border: `2px solid ${TIER_COLORS[cl.bridge.tier]}`,
45
47
  }} onError={e => { e.target.style.display = 'none'; }} />
@@ -67,13 +69,13 @@ export default function GridView({ connections, degree2 = [], onSelect, mode })
67
69
  {cl.members.slice(0, 8).map((m, i) => (
68
70
  <div key={m.id} style={{
69
71
  width: 22, height: 22, borderRadius: '50%', fontSize: 8, fontWeight: 700,
70
- background: m.profile_image_url ? 'transparent' : (TIER_COLORS[m.tier] || '#333'),
72
+ background: localPhoto(m.profile_image_url) ? 'transparent' : (TIER_COLORS[m.tier] || '#333'),
71
73
  color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center',
72
74
  border: '2px solid #0a0a1a', marginLeft: i > 0 ? -6 : 0, position: 'relative', zIndex: 8 - i,
73
75
  overflow: 'hidden',
74
76
  }}>
75
- {m.profile_image_url ? (
76
- <img src={m.profile_image_url} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
77
+ {localPhoto(m.profile_image_url) ? (
78
+ <img src={localPhoto(m.profile_image_url)} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
77
79
  ) : m.name?.charAt(0)}
78
80
  </div>
79
81
  ))}
@@ -131,8 +133,8 @@ export default function GridView({ connections, degree2 = [], onSelect, mode })
131
133
  onMouseLeave={e => e.currentTarget.style.background = 'rgba(255,255,255,0.03)'}
132
134
  >
133
135
  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
134
- {c.profile_image_url ? (
135
- <img src={c.profile_image_url} alt="" style={{
136
+ {localPhoto(c.profile_image_url) ? (
137
+ <img src={localPhoto(c.profile_image_url)} alt="" style={{
136
138
  width: 32, height: 32, borderRadius: '50%', objectFit: 'cover',
137
139
  border: `2px solid ${TIER_COLORS[tier]}`,
138
140
  }} onError={e => { e.target.style.display = 'none'; }} />
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- // Paths → Scores: the one-time offer to keep the curated list's old scores as
3
+ // The Scores tab: the one-time offer to keep the curated list's old scores as
4
4
  // your own, after the list was made neutral (lib/legacy-offer.js decides who
5
5
  // sees it and what "keep" writes). It sits at the top of the Scores tab until
6
6
  // it's answered, then never shows again. Not while a CSV import or the sample
@@ -1,9 +1,10 @@
1
1
  'use client';
2
2
 
3
- // The Scan page's view of what LinkedIn allows: the search budget, the cooldown
4
- // lock, and everyone whose list was only partly read, with a way back into each.
5
- // TRAPS §35. The numbers come from /api/scraper (lib/linkedin-limits.js,
6
- // lib/paused.js), which reads the same files the scanner writes.
3
+ // The Scan page's view of what LinkedIn allows: the search budget and the cap
4
+ // on profile views, the cooldown lock, and everyone whose list was only partly
5
+ // read, with a way back into each. TRAPS §16, §35. The numbers come from
6
+ // /api/scraper (lib/linkedin-limits.js, lib/paused.js), which reads the same
7
+ // files the scanner writes.
7
8
 
8
9
  import { useState } from 'react';
9
10
 
@@ -11,6 +12,7 @@ const LINE = '1px solid rgba(255,255,255,0.1)';
11
12
  const TIER = { S: '#FFD700', A: '#9B59B6', B: '#3498DB', C: '#95A5A6', D: '#BDC3C7' };
12
13
  const DAILY = [25, 50, 100, 200, 500];
13
14
  const MONTHLY = [100, 250, 500, 1000, 0];
15
+ const PROFILES = [10, 25, 50, 100];
14
16
 
15
17
  const when = (ms) => new Date(ms).toLocaleString(undefined, { weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });
16
18
  const day = (ms) => new Date(ms).toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
@@ -55,6 +57,8 @@ export function BudgetBox({ li, onSetLimits, disabled }) {
55
57
  if (!li) return null;
56
58
  const { limits } = li;
57
59
  const today = li.unreadable ? '?' : li.searchesToday;
60
+ const views = li.unreadable ? '?' : li.profilesToday;
61
+ const set = (change) => onSetLimits({ ...limits, ...change });
58
62
  return (
59
63
  <div style={{ padding: '12px 14px', borderRadius: 8, border: LINE, background: 'rgba(255,255,255,0.03)', fontSize: 12.5, color: '#b8c4c4' }}>
60
64
  <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(210px, 1fr))', gap: 14 }}>
@@ -66,16 +70,22 @@ export function BudgetBox({ li, onSetLimits, disabled }) {
66
70
  <div><b style={{ color: '#fff' }}>{li.searchesMonth}</b> of {limits.monthly || 'no limit'} this month · resets {day(li.monthResets)}</div>
67
71
  <Bar used={li.searchesMonth} cap={limits.monthly} />
68
72
  </div>
73
+ <div>
74
+ <div><b style={{ color: '#fff' }}>{views}</b> of {limits.profiles} profile views today</div>
75
+ <Bar used={li.unreadable ? limits.profiles : li.profilesToday} cap={limits.profiles} />
76
+ </div>
69
77
  </div>
70
78
  <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap', marginTop: 10 }}>
71
79
  <span>Budget:</span>
72
- <select value={limits.daily} disabled={disabled} onChange={(e) => onSetLimits({ daily: Number(e.target.value), monthly: limits.monthly })} style={sel}>
80
+ <select value={limits.daily} disabled={disabled} onChange={(e) => set({ daily: Number(e.target.value) })} style={sel}>
73
81
  {DAILY.map((n) => <option key={n} value={n}>{n} a day</option>)}
74
82
  </select>
75
- <select value={limits.monthly} disabled={disabled} onChange={(e) => onSetLimits({ daily: limits.daily, monthly: Number(e.target.value) })} style={sel}>
83
+ <select value={limits.monthly} disabled={disabled} onChange={(e) => set({ monthly: Number(e.target.value) })} style={sel}>
76
84
  {MONTHLY.map((n) => <option key={n} value={n}>{n ? `${n} a month` : 'no monthly cap (Premium)'}</option>)}
77
85
  </select>
78
- <span style={{ color: '#778' }}>{li.profilesToday} profile views today</span>
86
+ <select value={limits.profiles} disabled={disabled} onChange={(e) => set({ profiles: Number(e.target.value) })} style={sel}>
87
+ {PROFILES.map((n) => <option key={n} value={n}>{n} profile views a day</option>)}
88
+ </select>
79
89
  </div>
80
90
  <div style={{ marginTop: 8, color: '#778', lineHeight: 1.6 }}>
81
91
  Every page of someone&rsquo;s connections is one search. LinkedIn limits a free account&rsquo;s people
@@ -83,6 +93,11 @@ export function BudgetBox({ li, onSetLimits, disabled }) {
83
93
  When a budget is used, a scan saves what it read and stops; the next one carries on from the same page.
84
94
  {limits.daily > 100 && <b style={{ color: '#FFD700' }}> {limits.daily} a day can use up a free account&rsquo;s month in a day or two.</b>}
85
95
  </div>
96
+ <div style={{ marginTop: 6, color: '#778', lineHeight: 1.6 }}>
97
+ Each circle scan opens the person&rsquo;s profile once, which is one profile view. Profile views are what
98
+ LinkedIn restricted an account for, after about 20 in an hour. The scanner opens at most one a minute, and
99
+ once today&rsquo;s are used, a scan stops before the next profile and tries that person next time.
100
+ </div>
86
101
  </div>
87
102
  );
88
103
  }
@@ -1,5 +1,7 @@
1
1
  'use client';
2
2
 
3
+ import { localPhoto } from '../../lib/photos';
4
+
3
5
  const TIER_COLORS = { S: '#FFD700', A: '#9B59B6', B: '#3498DB', C: '#95A5A6', D: '#BDC3C7' };
4
6
 
5
7
  export default function ListView({ connections, degree2 = [], onSelect, mode }) {
@@ -47,8 +49,8 @@ export default function ListView({ connections, degree2 = [], onSelect, mode })
47
49
  >
48
50
  <span style={{ fontSize: 12, fontWeight: 700, color: '#444' }}>{i + 1}</span>
49
51
  <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
50
- {cl.bridge.profile_image_url ? (
51
- <img src={cl.bridge.profile_image_url} alt="" style={{
52
+ {localPhoto(cl.bridge.profile_image_url) ? (
53
+ <img src={localPhoto(cl.bridge.profile_image_url)} alt="" style={{
52
54
  width: 28, height: 28, borderRadius: '50%', objectFit: 'cover', flexShrink: 0,
53
55
  border: `2px solid ${TIER_COLORS[cl.bridge.tier]}`,
54
56
  }} onError={e => { e.target.style.display = 'none'; }} />
@@ -113,8 +115,8 @@ export default function ListView({ connections, degree2 = [], onSelect, mode })
113
115
  onMouseLeave={e => e.currentTarget.style.background = i < 3 ? `${TIER_COLORS[c.tier]}08` : 'transparent'}
114
116
  >
115
117
  <span style={{ fontSize: 12, fontWeight: 700, color: i < 3 ? TIER_COLORS[c.tier] : '#444' }}>{i + 1}</span>
116
- {c.profile_image_url ? (
117
- <img src={c.profile_image_url} alt="" style={{
118
+ {localPhoto(c.profile_image_url) ? (
119
+ <img src={localPhoto(c.profile_image_url)} alt="" style={{
118
120
  width: 28, height: 28, borderRadius: '50%', objectFit: 'cover',
119
121
  border: `2px solid ${TIER_COLORS[c.tier]}`,
120
122
  }} onError={e => { e.target.style.display = 'none'; }} />
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { TIER_COLORS_CLASSIC as TIER_COLORS } from '../../lib/tiers';
4
+ import { reachIndex, circleState } from '../../lib/reach';
4
5
 
5
6
  // How much of your 1st degree has had its circle opened.
6
7
  //
@@ -19,13 +20,11 @@ const CARD = {
19
20
  };
20
21
 
21
22
  export function computeMapping(degree1 = [], degree2 = [], skips = []) {
22
- const mappedIds = new Set(degree2.map((c) => c.source_connection_id).filter(Boolean));
23
- const hiddenUrls = new Set(skips.map((s) => s.profileUrl).filter(Boolean));
24
-
23
+ // One rule for whose circle is open, hidden or still to do: lib/reach.js.
24
+ const reach = reachIndex(degree1, degree2, { skips });
25
25
  const state = (c) => {
26
- if (mappedIds.has(c.id)) return 'mapped';
27
- if (c.profile_url && hiddenUrls.has(c.profile_url)) return 'hidden';
28
- return 'todo';
26
+ const s = circleState(c, reach);
27
+ return s === 'scanned' ? 'mapped' : s;
29
28
  };
30
29
 
31
30
  const counts = { mapped: 0, hidden: 0, todo: 0 };
@@ -24,7 +24,7 @@ export default function OnboardingGate({ children }) {
24
24
  ) : (
25
25
  <div style={{ maxWidth: 420 }}>
26
26
  <div style={{ fontSize: 18, fontWeight: 700, marginBottom: 8 }}>
27
- Can’t reach 6 Degrees’ local server
27
+ Can’t reach Six Degrees’ local server
28
28
  </div>
29
29
  <div style={{ color: '#888', fontSize: 14, lineHeight: 1.6 }}>
30
30
  The page loaded but the app behind it is not answering. If you started it
@@ -17,6 +17,8 @@
17
17
  */
18
18
 
19
19
  import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
20
+ import useRequests from './useRequests';
21
+ import { hasRequest } from '../../lib/requests-client';
20
22
  import {
21
23
  drag,
22
24
  forceCollide,
@@ -30,6 +32,7 @@ import {
30
32
  zoomTransform,
31
33
  } from 'd3';
32
34
  import { TIER_COLORS, TIER_ORDER, TIER_RING_RADIUS, initialsFor } from '../../lib/tiers';
35
+ import { localPhoto } from '../../lib/photos';
33
36
 
34
37
  const GOLDEN_ANGLE = 2.399963;
35
38
  const LABEL_ZOOM_THRESHOLD = 1.1;
@@ -71,6 +74,7 @@ const OrbitGraph = forwardRef(function OrbitGraph(
71
74
  const zoomKRef = useRef(1);
72
75
  const savedTransformRef = useRef(null);
73
76
  const applySelectionRef = useRef(() => {});
77
+ const applyRequestsRef = useRef(() => {});
74
78
  const focusNodeRef = useRef(() => {});
75
79
  const [size, setSize] = useState({ w: 0, h: 0 });
76
80
  const isMobile = useIsMobile();
@@ -122,6 +126,15 @@ const OrbitGraph = forwardRef(function OrbitGraph(
122
126
  applySelectionRef.current(selectedId);
123
127
  }, [selectedId]);
124
128
 
129
+ // So do requests you've sent (lib/requests-client.js): a request sent from
130
+ // any view rings that person's dots here at once.
131
+ const requests = useRequests();
132
+ const requestsRef = useRef(requests);
133
+ useEffect(() => {
134
+ requestsRef.current = requests;
135
+ applyRequestsRef.current(requests);
136
+ }, [requests]);
137
+
125
138
  useEffect(() => {
126
139
  const svgEl = svgRef.current;
127
140
  if (!svgEl || size.w < 40 || size.h < 40) return undefined;
@@ -362,7 +375,8 @@ const OrbitGraph = forwardRef(function OrbitGraph(
362
375
  .attr('font-size', d.r * 0.8).attr('font-weight', 700)
363
376
  .attr('fill', '#0b0b16').style('pointer-events', 'none');
364
377
 
365
- const imageUrl = isUser ? userImage : (conn?.profile_image_url ?? null);
378
+ // Only a photo saved on this computer (lib/photos.js).
379
+ const imageUrl = localPhoto(isUser ? userImage : conn?.profile_image_url);
366
380
  if (imageUrl) {
367
381
  const clipId = `og-clip-${safeId(d.id)}`;
368
382
  defs.append('clipPath').attr('id', clipId).append('circle').attr('r', d.r);
@@ -545,6 +559,23 @@ const OrbitGraph = forwardRef(function OrbitGraph(
545
559
  };
546
560
  applySelectionRef.current(selectedIdRef.current);
547
561
 
562
+ // ---- requests you've sent ---------------------------------------------
563
+ // A dashed gold ring on every 2nd-degree dot you've asked, in step with
564
+ // the shared list, without rebuilding the scene.
565
+ applyRequestsRef.current = (state) => {
566
+ nodesLayer.selectAll('circle.og-asked').remove();
567
+ node.filter((d) => d.kind === 'd2' && hasRequest(d.connection, state))
568
+ .append('circle')
569
+ .attr('class', 'og-asked')
570
+ .attr('r', (d) => d.r + 2.2)
571
+ .attr('fill', 'none')
572
+ .attr('stroke', '#FFD700')
573
+ .attr('stroke-width', 1)
574
+ .attr('stroke-dasharray', '2 1.6')
575
+ .style('pointer-events', 'none');
576
+ };
577
+ applyRequestsRef.current(requestsRef.current);
578
+
548
579
  // ---- fly-to ----------------------------------------------------------
549
580
  const nodeById = new Map(renderNodes.map((n) => [n.id, n]));
550
581
  focusNodeRef.current = (id) => {
@@ -591,6 +622,7 @@ const OrbitGraph = forwardRef(function OrbitGraph(
591
622
  return () => {
592
623
  sim?.stop();
593
624
  applySelectionRef.current = () => {};
625
+ applyRequestsRef.current = () => {};
594
626
  focusNodeRef.current = () => {};
595
627
  svg.on('.zoom', null);
596
628
  svg.on('click', null);
@@ -11,6 +11,7 @@ import { useMemo, useState } from 'react';
11
11
  import Link from 'next/link';
12
12
  import { buildQuest, XP_SEND, STAGE_SIZE } from '../../lib/quest';
13
13
  import { initialsFor } from '../../lib/tiers';
14
+ import { localPhoto } from '../../lib/photos';
14
15
 
15
16
  const TIER = { S: '#FFD700', A: '#9B59B6', B: '#3498DB', C: '#95A5A6', D: '#BDC3C7' };
16
17
  const LINE = '1px solid rgba(255,255,255,0.1)';
@@ -24,8 +25,8 @@ function Face({ person, size = 36, ring }) {
24
25
  display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: size * 0.34, fontWeight: 800,
25
26
  border: `2px solid ${ring || color}`,
26
27
  }}>{initialsFor(person?.name || '?')}</div>
27
- {person?.profile_image_url && (
28
- <img src={person.profile_image_url} alt="" onError={(e) => { e.currentTarget.style.display = 'none'; }}
28
+ {localPhoto(person?.profile_image_url) && (
29
+ <img src={localPhoto(person.profile_image_url)} alt="" onError={(e) => { e.currentTarget.style.display = 'none'; }}
29
30
  style={{ position: 'absolute', inset: 0, width: size, height: size, borderRadius: '50%', objectFit: 'cover', border: `2px solid ${ring || color}` }} />
30
31
  )}
31
32
  </div>
@@ -47,8 +48,8 @@ function Ring({ progress, size = 86, color = '#FF6B35', children }) {
47
48
  );
48
49
  }
49
50
 
50
- export default function OutlinkQuest({ recs, sentIds, added, mappedIds, onSend, onUndo }) {
51
- const quest = useMemo(() => buildQuest({ recs, sentIds, added, mappedIds }), [recs, sentIds, added, mappedIds]);
51
+ export default function OutlinkQuest({ recs, sentIds, added, mappedIds, reach, onSend, onUndo }) {
52
+ const quest = useMemo(() => buildQuest({ recs, sentIds, added, mappedIds, reach }), [recs, sentIds, added, mappedIds, reach]);
52
53
  const [open, setOpen] = useState(null); // bridge id of the expanded cluster
53
54
  const [toast, setToast] = useState(null);
54
55
  const { level } = quest;
@@ -505,7 +505,7 @@ function Section({ title, children }) {
505
505
  }
506
506
 
507
507
  // The company's score in people's power, where it comes from, and a control
508
- // to set it (Paths → Scores has every company).
508
+ // to set it (the Scores tab has every company).
509
509
  function CompanyScoreLine({ name }) {
510
510
  const { companies, setScore } = useCompanyScores();
511
511
  const c = companies?.find((x) => x.name === name);
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState } from 'react';
4
+ import { localPhoto } from '../../lib/photos';
4
5
 
5
6
  const TIER_COLORS = { S: '#FFD700', A: '#9B59B6', B: '#3498DB', C: '#95A5A6', D: '#BDC3C7' };
6
7
  const TIERS = ['S', 'A', 'B', 'C', 'D'];
@@ -58,8 +59,8 @@ export default function RingsView({ connections, degree2 = [], onSelect, mode })
58
59
  boxShadow: hoveredId === cl.bridge.id ? `0 0 20px ${TIER_COLORS[cl.bridge.tier]}60` : `0 0 10px ${TIER_COLORS[cl.bridge.tier]}20`,
59
60
  transition: 'box-shadow 0.2s',
60
61
  }}>
61
- {cl.bridge.profile_image_url ? (
62
- <img src={cl.bridge.profile_image_url} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
62
+ {localPhoto(cl.bridge.profile_image_url) ? (
63
+ <img src={localPhoto(cl.bridge.profile_image_url)} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
63
64
  ) : (
64
65
  <div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center',
65
66
  background: TIER_COLORS[cl.bridge.tier], color: cl.bridge.tier === 'S' ? '#000' : '#fff', fontSize: 20, fontWeight: 800 }}>
@@ -95,13 +96,13 @@ export default function RingsView({ connections, degree2 = [], onSelect, mode })
95
96
  <div style={{
96
97
  width: size, height: size, borderRadius: '50%',
97
98
  border: `2px solid ${TIER_COLORS[tier]}`,
98
- overflow: 'hidden', background: p.profile_image_url ? '#111' : TIER_COLORS[tier],
99
+ overflow: 'hidden', background: localPhoto(p.profile_image_url) ? '#111' : TIER_COLORS[tier],
99
100
  transition: 'transform 0.15s, box-shadow 0.15s',
100
101
  transform: isHov ? 'scale(1.3)' : 'scale(1)',
101
102
  boxShadow: isHov ? `0 0 12px ${TIER_COLORS[tier]}60` : 'none',
102
103
  }}>
103
- {p.profile_image_url ? (
104
- <img src={p.profile_image_url} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }}
104
+ {localPhoto(p.profile_image_url) ? (
105
+ <img src={localPhoto(p.profile_image_url)} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }}
105
106
  onError={e => { e.target.style.display = 'none'; e.target.parentElement.style.background = TIER_COLORS[tier]; }} />
106
107
  ) : (
107
108
  <span style={{ display: 'flex', width: '100%', height: '100%', alignItems: 'center', justifyContent: 'center',
@@ -202,7 +203,7 @@ export default function RingsView({ connections, degree2 = [], onSelect, mode })
202
203
  width: size, height: size, borderRadius: '50%',
203
204
  border: `2px solid ${TIER_COLORS[tier]}`,
204
205
  overflow: 'hidden',
205
- background: c.profile_image_url ? '#111' : TIER_COLORS[tier],
206
+ background: localPhoto(c.profile_image_url) ? '#111' : TIER_COLORS[tier],
206
207
  display: 'flex', alignItems: 'center', justifyContent: 'center',
207
208
  transition: 'transform 0.15s, box-shadow 0.15s',
208
209
  transform: isHovered ? 'scale(1.3)' : 'scale(1)',
@@ -210,8 +211,8 @@ export default function RingsView({ connections, degree2 = [], onSelect, mode })
210
211
  zIndex: isHovered ? 10 : 1,
211
212
  position: 'relative',
212
213
  }}>
213
- {c.profile_image_url ? (
214
- <img src={c.profile_image_url} alt="" style={{
214
+ {localPhoto(c.profile_image_url) ? (
215
+ <img src={localPhoto(c.profile_image_url)} alt="" style={{
215
216
  width: '100%', height: '100%', objectFit: 'cover',
216
217
  }} onError={e => {
217
218
  e.target.style.display = 'none';
@@ -20,7 +20,10 @@
20
20
  // appearing cannot feed a resize loop.
21
21
 
22
22
  import { memo, useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from 'react';
23
- import { separationPeople, compareWaysIn, summitLayout, shortName, TIERS } from '../../lib/separation';
23
+ import { separationPeople, compareWaysIn, summitLayout, shortName, TIERS, keyFor, routeIndex } from '../../lib/separation';
24
+ import { RARITY, rarityOf, rarityInfo, toggle } from '../../lib/rarity';
25
+ import { hasRequest } from '../../lib/requests-client';
26
+ import useRequests from './useRequests';
24
27
  import { initialsFor } from '../../lib/tiers';
25
28
  import Avatar from './Avatar';
26
29
 
@@ -63,7 +66,7 @@ function subline(person) {
63
66
  return person.headline || person.role || person.company || '';
64
67
  }
65
68
 
66
- export default function SeparationView({ connections = [], degree2 = [], onSelect, userName, selectedId, tierColors = CLASSIC }) {
69
+ export default function SeparationView({ connections = [], degree2 = [], fullDegree1 = connections, fullDegree2 = degree2, onSelect, userName, selectedId, tierColors = CLASSIC }) {
67
70
  const isMobile = useIsMobile();
68
71
  const ROW_H = isMobile ? 68 : 56;
69
72
  const K = isMobile ? 5 : 10;
@@ -73,6 +76,9 @@ export default function SeparationView({ connections = [], degree2 = [], onSelec
73
76
  const q = useDeferredValue(query.trim().toLowerCase());
74
77
  const [sort, setSort] = useState('power'); // 'power' | 'ways'
75
78
  const [tier, setTier] = useState('all');
79
+ // Rarity beside the tier (lib/rarity.js): any bands switched on; none is everyone.
80
+ const [rarities, setRarities] = useState(() => new Set());
81
+ const requests = useRequests();
76
82
  const [scrollY, setScrollY] = useState(0);
77
83
  const [box, setBox] = useState({ w: 0, h: 800 });
78
84
  // The scroll element lives in state as well as a ref: the ResizeObserver
@@ -90,11 +96,35 @@ export default function SeparationView({ connections = [], degree2 = [], onSelec
90
96
  () => (sort === 'ways' ? [...model.people].sort(compareWaysIn) : model.people),
91
97
  [model, sort],
92
98
  );
99
+ // How rare the way in to each person is, counted across every circle you've
100
+ // scanned (not only the ones a bridge-tier filter leaves): never a score.
101
+ const waysAll = useMemo(() => routeIndex(fullDegree2), [fullDegree2]);
102
+ const rarityBy = useMemo(() => {
103
+ const m = new Map();
104
+ for (const p of model.people) m.set(p.key, rarityOf(p.person, waysAll.get(p.key)?.size || p.waysIn));
105
+ return m;
106
+ }, [model, waysAll]);
107
+ const byRarity = useMemo(() => {
108
+ const out = Object.fromEntries(RARITY.map((r) => [r.key, 0]));
109
+ for (const r of rarityBy.values()) out[r.key]++;
110
+ return out;
111
+ }, [rarityBy]);
112
+ const matches = useCallback((p, text) => (tier === 'all' || p.tier === tier)
113
+ && (!rarities.size || rarities.has(rarityBy.get(p.key)?.key))
114
+ && (!text || p.haystack.includes(text)), [tier, rarities, rarityBy]);
93
115
  const visible = useMemo(() => {
94
- if (!q && tier === 'all') return ordered;
95
- return ordered.filter((p) => (tier === 'all' || p.tier === tier) && (!q || p.haystack.includes(q)));
96
- }, [ordered, q, tier]);
97
- const top = useMemo(() => visible.slice(0, K), [visible, K]);
116
+ if (!q && tier === 'all' && !rarities.size) return ordered;
117
+ return ordered.filter((p) => matches(p, q));
118
+ }, [ordered, q, tier, rarities, matches]);
119
+
120
+ // Who you've already asked, or already know. The map is "who to ask next",
121
+ // so it moves on past them: send requests to its ten and the next ten come
122
+ // up. The list still shows everyone, marked.
123
+ const d1Keys = useMemo(() => new Set(fullDegree1.map(keyFor)), [fullDegree1]);
124
+ const statusOf = useCallback((p) => (d1Keys.has(p.key) ? 'connected' : hasRequest(p.person, requests) ? 'asked' : null),
125
+ [d1Keys, requests]);
126
+ const top = useMemo(() => visible.filter((p) => !statusOf(p)).slice(0, K), [visible, K, statusOf]);
127
+ const askedShown = useMemo(() => visible.reduce((n, p) => n + (statusOf(p) ? 1 : 0), 0), [visible, statusOf]);
98
128
  const selectedKey = selectedId != null ? model.rowToKey.get(selectedId) ?? null : null;
99
129
 
100
130
  // onSelect is read through a ref so the row and map callbacks never change
@@ -157,12 +187,14 @@ export default function SeparationView({ connections = [], degree2 = [], onSelec
157
187
 
158
188
  const changeQuery = (v) => { setQuery(v); toTop(); };
159
189
  const changeTier = (t) => { setTier(t); toTop(); };
190
+ const changeRarity = (r) => { setRarities((set) => toggle(set, r)); toTop(); };
191
+ const clearAll = () => { setQuery(''); setTier('all'); setRarities(new Set()); toTop(); };
160
192
  const changeSort = (s) => { setSort(s); toTop(); };
161
193
  const onSearchKey = (e) => {
162
194
  if (e.key === 'Enter') {
163
195
  // Read the live text, not the deferred copy the list is still catching up to.
164
196
  const live = query.trim().toLowerCase();
165
- const first = ordered.find((p) => (tier === 'all' || p.tier === tier) && (!live || p.haystack.includes(live)));
197
+ const first = ordered.find((p) => matches(p, live));
166
198
  if (first) onPick(first);
167
199
  } else if (e.key === 'Escape') {
168
200
  if (query) changeQuery('');
@@ -171,7 +203,7 @@ export default function SeparationView({ connections = [], degree2 = [], onSelec
171
203
  };
172
204
 
173
205
  const { summary } = model;
174
- const filtered = !!q || tier !== 'all';
206
+ const filtered = !!q || tier !== 'all' || rarities.size > 0;
175
207
  const youLabel = !userName || /^you$/i.test(String(userName).trim()) ? 'You' : initialsFor(userName);
176
208
  const rows = visible.slice(start, end);
177
209
 
@@ -276,6 +308,23 @@ export default function SeparationView({ connections = [], degree2 = [], onSelec
276
308
  </button>
277
309
  );
278
310
  })}
311
+ <span title={RARITY_NOTE} style={{ fontSize: 10, color: '#666', whiteSpace: 'nowrap', flexShrink: 0, textTransform: 'uppercase', letterSpacing: 0.5, marginLeft: 6, cursor: 'help' }}>Rarity</span>
312
+ {RARITY.map((r) => {
313
+ const on = rarities.has(r.key);
314
+ return (
315
+ <button key={r.key} type="button" aria-pressed={on} onClick={() => changeRarity(r.key)}
316
+ title={`${r.label}: ${r.range} mutual connection${r.range === '1' ? '' : 's'}. ${RARITY_NOTE}`} style={{
317
+ height: isMobile ? 40 : 30, padding: '0 10px', borderRadius: 15, cursor: 'pointer', flexShrink: 0,
318
+ fontSize: 12, fontWeight: 700, whiteSpace: 'nowrap',
319
+ border: `1px solid ${on ? r.color : 'rgba(255,255,255,0.1)'}`,
320
+ background: on ? `${r.color}22` : 'rgba(255,255,255,0.03)',
321
+ color: on ? r.color : '#999',
322
+ }}>
323
+ {r.label}
324
+ <span style={{ marginLeft: 5, fontWeight: 500, color: on ? r.color : '#666', opacity: 0.85 }}>{fmt(byRarity[r.key])}</span>
325
+ </button>
326
+ );
327
+ })}
279
328
  </div>
280
329
  </div>
281
330
 
@@ -290,9 +339,10 @@ export default function SeparationView({ connections = [], degree2 = [], onSelec
290
339
  <div style={{ height: mapBlockH, boxSizing: 'border-box', paddingTop: 10 }}>
291
340
  <div style={{ height: captionH - 10, overflow: 'hidden' }}>
292
341
  <div style={{ fontSize: 12, fontWeight: 700, color: '#ddd', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
293
- Top {fmt(top.length)} of {fmt(visible.length)}{filtered ? ' shown' : ''} · every way in drawn
342
+ Top {fmt(top.length)} you haven’t asked, of {fmt(visible.length)}{filtered ? ' shown' : ''} · every way in drawn
294
343
  </div>
295
344
  <div style={{ fontSize: 10, color: '#777', marginTop: 2, whiteSpace: isMobile ? 'normal' : 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', lineHeight: 1.35 }}>
345
+ {askedShown > 0 && <span style={{ color: '#FFD700' }}>{fmt(askedShown)} asked or connected, so it moved on · </span>}
296
346
  <span style={{ color: ORANGE }}>solid orange</span> = top-scored bridge · dashed = other routes · dot size = ways in
297
347
  </div>
298
348
  </div>
@@ -335,6 +385,8 @@ export default function SeparationView({ connections = [], degree2 = [], onSelec
335
385
  tierColors={tierColors}
336
386
  onPick={onPick}
337
387
  q={q}
388
+ status={statusOf(p)}
389
+ rarity={rarityBy.get(p.key)}
338
390
  />
339
391
  ))}
340
392
 
@@ -342,9 +394,9 @@ export default function SeparationView({ connections = [], degree2 = [], onSelec
342
394
  <div style={{ position: 'absolute', top: 40, left: 0, right: 0, textAlign: 'center', color: '#888', fontSize: 13, padding: '0 16px' }}>
343
395
  {q
344
396
  ? <>No one matches ‘{query.trim()}’. Search covers names, headlines, companies and who knows them.</>
345
- : <>No {tier}-tier people here.</>}
397
+ : <>No one here with those filters.</>}
346
398
  <div style={{ marginTop: 12 }}>
347
- <button type="button" onClick={() => { setQuery(''); setTier('all'); toTop(); }} style={{
399
+ <button type="button" onClick={clearAll} style={{
348
400
  padding: '8px 16px', borderRadius: 8, cursor: 'pointer', fontSize: 12, fontWeight: 700,
349
401
  border: '1px solid rgba(255,255,255,0.15)', background: 'rgba(255,255,255,0.06)', color: '#fff',
350
402
  }}>Clear</button>
@@ -495,7 +547,33 @@ function Via({ p, route, tierColors, compact }) {
495
547
  );
496
548
  }
497
549
 
498
- const Row = memo(function Row({ p, top, height, isMobile, selected, tierColors, onPick, q }) {
550
+ /** "Request sent" or "Connected", and how rare the way in is. */
551
+ function Tags({ status, rarity }) {
552
+ const info = rarity ? rarityInfo(rarity.key) : null;
553
+ return (
554
+ <>
555
+ {status === 'asked' && (
556
+ <span title="You sent a request. It shows everywhere in the app." style={{
557
+ flexShrink: 0, fontSize: 9, fontWeight: 800, color: '#FFD700', border: '1px dashed rgba(255,215,0,0.6)', borderRadius: 4, padding: '0 4px',
558
+ }}>Request sent</span>
559
+ )}
560
+ {status === 'connected' && (
561
+ <span title="Already one of your connections" style={{
562
+ flexShrink: 0, fontSize: 9, fontWeight: 800, color: '#00ff88', border: '1px solid rgba(0,255,136,0.5)', borderRadius: 4, padding: '0 4px',
563
+ }}>Connected</span>
564
+ )}
565
+ {info && status !== 'connected' && (
566
+ <span title={`${rarity.count} mutual connection${rarity.count === 1 ? '' : 's'}${rarity.from === 'scans' ? ' (from your scans, so it can only go up)' : ''}. ${RARITY_NOTE}`} style={{
567
+ flexShrink: 0, fontSize: 9, fontWeight: 700, color: info.color, border: `1px solid ${info.color}55`, borderRadius: 4, padding: '0 4px',
568
+ }}>{info.label}</span>
569
+ )}
570
+ </>
571
+ );
572
+ }
573
+
574
+ const RARITY_NOTE = 'Rarity is how many mutual connections lead to them: few is a rare way in, many is warm (likely to accept). It never changes a score or a tier.';
575
+
576
+ const Row = memo(function Row({ p, top, height, isMobile, selected, tierColors, onPick, q, status, rarity }) {
499
577
  const { person } = p;
500
578
  const c = tierColors[p.tier] || '#888';
501
579
  const route = pickRoute(p, q);
@@ -532,8 +610,11 @@ const Row = memo(function Row({ p, top, height, isMobile, selected, tierColors,
532
610
 
533
611
  {isMobile ? (
534
612
  <div style={{ minWidth: 0, display: 'flex', flexDirection: 'column', gap: 2 }}>
535
- <span style={{ ...ellipsis, fontSize: 13, fontWeight: 600, color: member ? '#888' : '#fff' }}>
536
- {person.name}{member && <span style={{ fontSize: 10, color: '#666', fontWeight: 400 }}> · out of network</span>}
613
+ <span style={{ display: 'flex', alignItems: 'center', gap: 4, minWidth: 0 }}>
614
+ <span style={{ ...ellipsis, fontSize: 13, fontWeight: 600, color: member ? '#888' : '#fff', minWidth: 0 }}>
615
+ {person.name}{member && <span style={{ fontSize: 10, color: '#666', fontWeight: 400 }}> · out of network</span>}
616
+ </span>
617
+ <Tags status={status} rarity={rarity} />
537
618
  </span>
538
619
  <Via p={p} route={route} tierColors={tierColors} compact />
539
620
  <span style={{ ...ellipsis, fontSize: 10, color: '#666' }}>{subline(person)}</span>
@@ -544,6 +625,7 @@ const Row = memo(function Row({ p, top, height, isMobile, selected, tierColors,
544
625
  <div style={{ display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 }}>
545
626
  <span style={{ ...ellipsis, fontSize: 13, fontWeight: 600, color: member ? '#888' : '#fff' }}>{person.name}</span>
546
627
  <span style={{ flexShrink: 0, fontSize: 9, fontWeight: 800, color: c, border: `1px solid ${c}55`, borderRadius: 4, padding: '0 4px' }}>{p.tier}</span>
628
+ <Tags status={status} rarity={rarity} />
547
629
  {member && <span style={{ flexShrink: 0, fontSize: 10, color: '#666' }}>out of network</span>}
548
630
  </div>
549
631
  <div style={{ ...ellipsis, fontSize: 10, color: '#777', marginTop: 2 }}>{subline(person)}</div>