six-degrees 0.5.0 → 0.6.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 (366) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +14 -14
  4. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  5. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
  10. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  11. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  13. package/.next/standalone/.next/server/app/_not-found.rsc +20 -19
  14. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +20 -19
  15. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +16 -15
  16. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  17. package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
  18. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
  19. package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/social/crm/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/update/route.js +1 -1
  45. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/import/page.js +1 -1
  50. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  52. package/.next/standalone/.next/server/app/import.html +1 -1
  53. package/.next/standalone/.next/server/app/import.rsc +24 -23
  54. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +24 -23
  55. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
  56. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +18 -17
  57. package/.next/standalone/.next/server/app/index.html +1 -1
  58. package/.next/standalone/.next/server/app/index.rsc +22 -21
  59. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +18 -17
  60. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +22 -21
  61. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  62. package/.next/standalone/.next/server/app/launch/page.js +1 -1
  63. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  64. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  65. package/.next/standalone/.next/server/app/launch.html +2 -2
  66. package/.next/standalone/.next/server/app/launch.rsc +24 -23
  67. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +24 -23
  68. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
  69. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +18 -17
  70. package/.next/standalone/.next/server/app/page.js +1 -1
  71. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  72. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  73. package/.next/standalone/.next/server/app/paths/page.js +1 -1
  74. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  75. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  76. package/.next/standalone/.next/server/app/paths.html +1 -1
  77. package/.next/standalone/.next/server/app/paths.rsc +24 -23
  78. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +24 -23
  79. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
  80. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +18 -17
  81. package/.next/standalone/.next/server/app/profile/page.js +1 -1
  82. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  83. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  84. package/.next/standalone/.next/server/app/profile.html +1 -1
  85. package/.next/standalone/.next/server/app/profile.rsc +24 -23
  86. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +24 -23
  87. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
  88. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +18 -17
  89. package/.next/standalone/.next/server/app/queue/page.js +1 -1
  90. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  91. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  92. package/.next/standalone/.next/server/app/queue.html +1 -1
  93. package/.next/standalone/.next/server/app/queue.rsc +24 -23
  94. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +24 -23
  95. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
  96. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +18 -17
  97. package/.next/standalone/.next/server/app/scores/page.js +1 -1
  98. package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
  99. package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
  100. package/.next/standalone/.next/server/app/scores.html +1 -1
  101. package/.next/standalone/.next/server/app/scores.rsc +24 -23
  102. package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +24 -23
  103. package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
  104. package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +18 -17
  105. package/.next/standalone/.next/server/app/settings/page.js +1 -1
  106. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  107. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  108. package/.next/standalone/.next/server/app/settings.html +1 -1
  109. package/.next/standalone/.next/server/app/settings.rsc +24 -23
  110. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +24 -23
  111. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  112. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +18 -17
  113. package/.next/standalone/.next/server/app/setup/page.js +1 -1
  114. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  115. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  116. package/.next/standalone/.next/server/app/setup.html +1 -1
  117. package/.next/standalone/.next/server/app/setup.rsc +24 -23
  118. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +24 -23
  119. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
  120. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +18 -17
  121. package/.next/standalone/.next/server/app/social/page.js +1 -1
  122. package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
  123. package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
  124. package/.next/standalone/.next/server/app/social.html +1 -1
  125. package/.next/standalone/.next/server/app/social.rsc +24 -23
  126. package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +24 -23
  127. package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +2 -2
  128. package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +18 -17
  129. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +1 -1
  130. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05_3-22._.js +1 -1
  132. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +1 -1
  133. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +1 -1
  134. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +1 -1
  135. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +1 -1
  136. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +1 -1
  137. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +1 -1
  138. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
  139. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0um1uey._.js +1 -1
  140. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +1 -1
  141. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +1 -1
  142. package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +1 -1
  143. package/.next/standalone/.next/server/chunks/[root-of-the-server]__13dx4sd._.js +1 -1
  144. package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +1 -1
  145. package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/[root-of-the-server]__15wc9e-._.js +1 -1
  147. package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +1 -1
  148. package/.next/standalone/.next/server/chunks/[root-of-the-server]__18bcnu4._.js +3 -0
  149. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +1 -1
  150. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +2 -2
  151. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +1 -1
  152. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +1 -1
  153. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
  154. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +1 -1
  155. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +1 -1
  156. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tir0vc._.js +2 -2
  157. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +1 -1
  158. package/.next/standalone/.next/server/chunks/[root-of-the-server]__20rwfdw._.js +2 -2
  159. package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -7
  160. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +2 -2
  161. package/.next/standalone/.next/server/chunks/lib_0851sk0._.js +5 -5
  162. package/.next/standalone/.next/server/chunks/lib_1q7p2ro._.js +5 -5
  163. package/.next/standalone/.next/server/chunks/lib_1r6l86l._.js +5 -5
  164. package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +9 -9
  165. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08oxiqh._.js +3 -0
  166. package/.next/standalone/.next/server/chunks/ssr/_03l7vge._.js +1 -1
  167. package/.next/standalone/.next/server/chunks/ssr/_08g31gm._.js +3 -0
  168. package/.next/standalone/.next/server/chunks/ssr/_0gk_ofx._.js +3 -0
  169. package/.next/standalone/.next/server/chunks/ssr/_0nwsf93._.js +3 -0
  170. package/.next/standalone/.next/server/chunks/ssr/_0vho1lb._.js +3 -0
  171. package/.next/standalone/.next/server/chunks/ssr/{_0jdkqgi._.js → _1-658fq._.js} +2 -2
  172. package/.next/standalone/.next/server/chunks/ssr/_1bc46kz._.js +3 -0
  173. package/.next/standalone/.next/server/chunks/ssr/_1f_n00p._.js +3 -0
  174. package/.next/standalone/.next/server/chunks/ssr/_1fm1ih_._.js +9 -0
  175. package/.next/standalone/.next/server/chunks/ssr/_1k2nmbw._.js +9 -0
  176. package/.next/standalone/.next/server/chunks/ssr/_2105jz8._.js +3 -0
  177. package/.next/standalone/.next/server/chunks/ssr/app_141w189._.js +1 -1
  178. package/.next/standalone/.next/server/chunks/ssr/app_components_0t908t-._.js +3 -0
  179. package/.next/standalone/.next/server/chunks/ssr/app_components_0uuff77._.js +13 -0
  180. package/.next/standalone/.next/server/chunks/ssr/app_components_1j3q-h5._.js +13 -0
  181. package/.next/standalone/.next/server/chunks/ssr/{_0gtv9jb._.js → app_components_CompanyScores_1g0mytz.js} +2 -2
  182. package/.next/standalone/.next/server/chunks/ssr/app_components_settings_SectorPicker_1c-29qw.js +1 -1
  183. package/.next/standalone/.next/server/chunks/ssr/app_import_page_1fmu6_l.js +1 -1
  184. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +14 -12
  185. package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +2 -2
  186. package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
  187. package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +6 -6
  188. package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
  189. package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +4 -4
  190. package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +1 -1
  191. package/.next/standalone/.next/server/chunks/ssr/{lib_0i8v-s3._.js → lib_1c4ieuw._.js} +2 -2
  192. package/.next/standalone/.next/server/chunks/ssr/lib_galaxy-lab_108_lu-.js +1 -1
  193. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  194. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  195. package/.next/standalone/.next/server/pages/404.html +1 -1
  196. package/.next/standalone/.next/server/pages/500.html +1 -1
  197. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  198. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  199. package/.next/standalone/.next/static/chunks/02pq76u7y4v9-.js +22 -0
  200. package/.next/standalone/.next/static/chunks/0cmq0exd4d0h9.js +1 -0
  201. package/.next/{static/chunks/2wgz9ietkqqe6.js → standalone/.next/static/chunks/0i02yxppzds1o.js} +1 -1
  202. package/.next/standalone/.next/static/chunks/0m_p4k-q2qh--.js +1 -0
  203. package/.next/standalone/.next/static/chunks/0oyslj62jkubd.js +1 -0
  204. package/.next/standalone/.next/static/chunks/{1x_ysly42-teq.css → 0vo-l8hxneba6.css} +1 -1
  205. package/.next/{static/chunks/3sowbvbaj1s6n.js → standalone/.next/static/chunks/0ws7zy0w57ufi.js} +1 -1
  206. package/.next/standalone/.next/static/chunks/0yh8z2tbrc5i_.js +1 -0
  207. package/.next/standalone/.next/static/chunks/172e10mi-tpo_.js +34 -0
  208. package/.next/standalone/.next/static/chunks/1ei5vbe2uw6fl.js +1 -0
  209. package/.next/standalone/.next/static/chunks/1f7_pl_wq7lh3.js +11 -0
  210. package/.next/standalone/.next/static/chunks/1jzbe7zdx9vbj.js +2 -0
  211. package/.next/standalone/.next/static/chunks/1m3017gp790pw.js +45 -0
  212. package/.next/standalone/.next/static/chunks/1t20e8u0st7ep.js +1 -0
  213. package/.next/standalone/.next/static/chunks/2-ch9rm27eril.js +11 -0
  214. package/.next/standalone/.next/static/chunks/2ldv-v3s9yaif.js +1 -0
  215. package/.next/standalone/.next/static/chunks/3bp284gjf7dyq.js +1 -0
  216. package/.next/standalone/.next/static/chunks/3k1f6clbn3gj3.js +7 -0
  217. package/.next/standalone/.next/static/chunks/3l4t8je6iguzc.js +1 -0
  218. package/.next/standalone/.next/static/chunks/{0muwt9bykzr-1.js → 3tplzuc9jk-tl.js} +1 -1
  219. package/.next/standalone/.next/static/chunks/42m0bqr8pbovz.js +11 -0
  220. package/.next/standalone/CHANGELOG.md +61 -0
  221. package/.next/standalone/README.md +22 -8
  222. package/.next/standalone/app/api/scraper/route.js +40 -3
  223. package/.next/standalone/app/api/update/route.js +12 -6
  224. package/.next/standalone/app/components/AppHeader.js +257 -0
  225. package/.next/standalone/app/components/Avatar.js +2 -1
  226. package/.next/standalone/app/components/ChainView.js +12 -12
  227. package/.next/standalone/app/components/FilterPanel.js +20 -12
  228. package/.next/standalone/app/components/ForceGraph.js +163 -25
  229. package/.next/standalone/app/components/GalaxyLab.js +43 -12
  230. package/.next/standalone/app/components/GridView.js +4 -4
  231. package/.next/standalone/app/components/LinkedInLimits.js +2 -1
  232. package/.next/standalone/app/components/ListView.js +3 -3
  233. package/.next/standalone/app/components/NoteDetail.js +2 -1
  234. package/.next/standalone/app/components/OnboardingGate.js +2 -2
  235. package/.next/standalone/app/components/OutlinkQuest.js +2 -1
  236. package/.next/standalone/app/components/PathsAnalyzer.js +3 -2
  237. package/.next/standalone/app/components/PeopleMap.js +2 -1
  238. package/.next/standalone/app/components/RingsView.js +3 -3
  239. package/.next/standalone/app/components/ScanTrail.js +85 -0
  240. package/.next/standalone/app/components/SeparationView.js +100 -77
  241. package/.next/standalone/app/components/Sidebar.js +4 -4
  242. package/.next/standalone/app/components/TheirCircle.js +2 -1
  243. package/.next/standalone/app/components/ThemeLoader.js +9 -0
  244. package/.next/standalone/app/components/UpdatePanel.js +20 -2
  245. package/.next/standalone/app/components/settings/AppearanceSection.js +226 -0
  246. package/.next/standalone/app/components/settings/NetworkHealthSection.js +2 -1
  247. package/.next/standalone/app/components/ui.js +2 -1
  248. package/.next/standalone/app/globals.css +70 -2
  249. package/.next/standalone/app/import/page.js +3 -3
  250. package/.next/standalone/app/layout.js +11 -2
  251. package/.next/standalone/app/page.js +69 -215
  252. package/.next/standalone/app/paths/page.js +8 -21
  253. package/.next/standalone/app/profile/page.js +11 -10
  254. package/.next/standalone/app/queue/page.js +8 -21
  255. package/.next/standalone/app/scores/page.js +1 -1
  256. package/.next/standalone/app/settings/page.js +7 -3
  257. package/.next/standalone/app/setup/page.js +83 -88
  258. package/.next/standalone/app/social/Crm.js +2 -1
  259. package/.next/standalone/app/social/SocialHub.js +2 -2
  260. package/.next/standalone/app/social/page.js +1 -1
  261. package/.next/standalone/lib/data-import.js +1 -0
  262. package/.next/standalone/lib/galaxy-export.js +2 -1
  263. package/.next/standalone/lib/galaxy-lab.js +24 -8
  264. package/.next/standalone/lib/level.js +51 -0
  265. package/.next/standalone/lib/release.js +13 -7
  266. package/.next/standalone/lib/scanner-python.js +38 -8
  267. package/.next/standalone/lib/scraper-client.js +2 -1
  268. package/.next/standalone/lib/separation.js +4 -2
  269. package/.next/standalone/lib/theme-store.js +61 -0
  270. package/.next/standalone/lib/themes.js +186 -0
  271. package/.next/standalone/lib/tiers.js +2 -8
  272. package/.next/standalone/package-lock.json +2 -2
  273. package/.next/standalone/package.json +4 -4
  274. package/.next/standalone/scripts/build-desktop.mjs +325 -0
  275. package/.next/standalone/scripts/build-site.mjs +85 -15
  276. package/.next/standalone/scripts/gen-test-network.mjs +93 -0
  277. package/.next/standalone/scripts/next.mjs +20 -0
  278. package/.next/standalone/scripts/readme_buttons.py +3 -0
  279. package/.next/standalone/scripts/requirements.txt +18 -1
  280. package/.next/standalone/scripts/site-news.mjs +54 -9
  281. package/.next/standalone/scripts/smoke-desktop.mjs +177 -0
  282. package/.next/standalone/scripts/windows/six-degrees.iss +100 -0
  283. package/.next/static/chunks/02pq76u7y4v9-.js +22 -0
  284. package/.next/static/chunks/0cmq0exd4d0h9.js +1 -0
  285. package/.next/{standalone/.next/static/chunks/2wgz9ietkqqe6.js → static/chunks/0i02yxppzds1o.js} +1 -1
  286. package/.next/static/chunks/0m_p4k-q2qh--.js +1 -0
  287. package/.next/static/chunks/0oyslj62jkubd.js +1 -0
  288. package/.next/static/chunks/{1x_ysly42-teq.css → 0vo-l8hxneba6.css} +1 -1
  289. package/.next/{standalone/.next/static/chunks/3sowbvbaj1s6n.js → static/chunks/0ws7zy0w57ufi.js} +1 -1
  290. package/.next/static/chunks/0yh8z2tbrc5i_.js +1 -0
  291. package/.next/static/chunks/172e10mi-tpo_.js +34 -0
  292. package/.next/static/chunks/1ei5vbe2uw6fl.js +1 -0
  293. package/.next/static/chunks/1f7_pl_wq7lh3.js +11 -0
  294. package/.next/static/chunks/1jzbe7zdx9vbj.js +2 -0
  295. package/.next/static/chunks/1m3017gp790pw.js +45 -0
  296. package/.next/static/chunks/1t20e8u0st7ep.js +1 -0
  297. package/.next/static/chunks/2-ch9rm27eril.js +11 -0
  298. package/.next/static/chunks/2ldv-v3s9yaif.js +1 -0
  299. package/.next/static/chunks/3bp284gjf7dyq.js +1 -0
  300. package/.next/static/chunks/3k1f6clbn3gj3.js +7 -0
  301. package/.next/static/chunks/3l4t8je6iguzc.js +1 -0
  302. package/.next/static/chunks/{0muwt9bykzr-1.js → 3tplzuc9jk-tl.js} +1 -1
  303. package/.next/static/chunks/42m0bqr8pbovz.js +11 -0
  304. package/CHANGELOG.md +61 -0
  305. package/README.md +22 -8
  306. package/lib/data-import.js +1 -0
  307. package/lib/galaxy-export.js +2 -1
  308. package/lib/galaxy-lab.js +24 -8
  309. package/lib/level.js +51 -0
  310. package/lib/release.js +13 -7
  311. package/lib/scanner-python.js +38 -8
  312. package/lib/scraper-client.js +2 -1
  313. package/lib/separation.js +4 -2
  314. package/lib/theme-store.js +61 -0
  315. package/lib/themes.js +186 -0
  316. package/lib/tiers.js +2 -8
  317. package/package.json +4 -4
  318. package/scripts/requirements.txt +18 -1
  319. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljcioj._.js +0 -3
  320. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16hs8kw._.js +0 -3
  321. package/.next/standalone/.next/server/chunks/ssr/_01kacen._.js +0 -3
  322. package/.next/standalone/.next/server/chunks/ssr/_0xi-fbs._.js +0 -3
  323. package/.next/standalone/.next/server/chunks/ssr/_0xmwmfv._.js +0 -3
  324. package/.next/standalone/.next/server/chunks/ssr/_1-b9hj7._.js +0 -9
  325. package/.next/standalone/.next/server/chunks/ssr/_112ewem._.js +0 -3
  326. package/.next/standalone/.next/server/chunks/ssr/_16ab63q._.js +0 -3
  327. package/.next/standalone/.next/server/chunks/ssr/_1fxm0-s._.js +0 -3
  328. package/.next/standalone/.next/server/chunks/ssr/_1ra1d3r._.js +0 -9
  329. package/.next/standalone/.next/server/chunks/ssr/app_components_ui_0pdmz2z.js +0 -3
  330. package/.next/standalone/.next/server/chunks/ssr/lib_13rapwj._.js +0 -3
  331. package/.next/standalone/.next/static/chunks/03bziijw5srol.js +0 -1
  332. package/.next/standalone/.next/static/chunks/0bgq8wmopl_ig.js +0 -7
  333. package/.next/standalone/.next/static/chunks/0ezus6p2eb-vg.js +0 -1
  334. package/.next/standalone/.next/static/chunks/0g_fy2034bp0x.js +0 -34
  335. package/.next/standalone/.next/static/chunks/0q5dru4q6gxl5.js +0 -22
  336. package/.next/standalone/.next/static/chunks/0w-byiwb2zax8.js +0 -2
  337. package/.next/standalone/.next/static/chunks/1g7jcovdejc9-.js +0 -1
  338. package/.next/standalone/.next/static/chunks/1i51yylxdzht1.js +0 -1
  339. package/.next/standalone/.next/static/chunks/1zf8swaasjf1e.js +0 -43
  340. package/.next/standalone/.next/static/chunks/2dwtoszeq91us.js +0 -1
  341. package/.next/standalone/.next/static/chunks/2rqtpccruavf2.js +0 -1
  342. package/.next/standalone/.next/static/chunks/30x3oq_st7a5r.js +0 -1
  343. package/.next/standalone/.next/static/chunks/355ryio1b7sm1.js +0 -1
  344. package/.next/standalone/.next/static/chunks/3ap5hgma7n0o7.js +0 -1
  345. package/.next/standalone/.next/static/chunks/3fci6qi39-1n5.js +0 -1
  346. package/.next/static/chunks/03bziijw5srol.js +0 -1
  347. package/.next/static/chunks/0bgq8wmopl_ig.js +0 -7
  348. package/.next/static/chunks/0ezus6p2eb-vg.js +0 -1
  349. package/.next/static/chunks/0g_fy2034bp0x.js +0 -34
  350. package/.next/static/chunks/0q5dru4q6gxl5.js +0 -22
  351. package/.next/static/chunks/0w-byiwb2zax8.js +0 -2
  352. package/.next/static/chunks/1g7jcovdejc9-.js +0 -1
  353. package/.next/static/chunks/1i51yylxdzht1.js +0 -1
  354. package/.next/static/chunks/1zf8swaasjf1e.js +0 -43
  355. package/.next/static/chunks/2dwtoszeq91us.js +0 -1
  356. package/.next/static/chunks/2rqtpccruavf2.js +0 -1
  357. package/.next/static/chunks/30x3oq_st7a5r.js +0 -1
  358. package/.next/static/chunks/355ryio1b7sm1.js +0 -1
  359. package/.next/static/chunks/3ap5hgma7n0o7.js +0 -1
  360. package/.next/static/chunks/3fci6qi39-1n5.js +0 -1
  361. /package/.next/standalone/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_buildManifest.js +0 -0
  362. /package/.next/standalone/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_clientMiddlewareManifest.js +0 -0
  363. /package/.next/standalone/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_ssgManifest.js +0 -0
  364. /package/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_buildManifest.js +0 -0
  365. /package/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_clientMiddlewareManifest.js +0 -0
  366. /package/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_ssgManifest.js +0 -0
@@ -14,6 +14,7 @@
14
14
  # Drives the user's own Google Chrome (channel="chrome"), so no browser is downloaded.
15
15
  # Its wheel carries Playwright's driver, which it needs.
16
16
  playwright==1.63.0 \
17
+ --hash=sha256:2f9a707a6c6c91157ed77bff2b8caeb04b3c8d46e70d585fc134298cbe4b5cc6 \
17
18
  --hash=sha256:354e15b29503565fc598b89f16fbe070459343bef9d7498a93e304864000c6a7 \
18
19
  --hash=sha256:84c540759e8e7f01e690e197e04060f48899d37cea322299f255843273d3385d \
19
20
  --hash=sha256:ad21bc07516b187965a7521c5cf0df0bd657b17482eaad74335272d35a2b07de \
@@ -24,17 +25,22 @@ playwright==1.63.0 \
24
25
  greenlet==3.5.6 \
25
26
  --hash=sha256:1c20ea32a73d17b9b60e3371240e17b0068120c98a5ec01a224a7dd8c89733ba \
26
27
  --hash=sha256:45bfd2b51e38aaa5f9849f114d9c7c1d75f69187c849b3549cd64c465283abfa \
28
+ --hash=sha256:5b602b4201b965a8354d74e232364a66ff243dd142e350d035f46169bb36e13d \
29
+ --hash=sha256:7ac4abb3877c43af320392c664774eef6fa2cc063c79a55fc02d844a3cbe7395 \
27
30
  --hash=sha256:8cddea1b8339451c2fb3388e138347b6126744f33b611bdb55b7357361cfef46 \
28
31
  --hash=sha256:95e7c44d072db623a1aab04ce488cf9533294a77ed9d072cd503a3596f4106ac \
29
32
  --hash=sha256:975736b002ed080d124cf81a79cb7e05cb26d6b3f5c7a7b651c0fcce70353aa1 \
30
33
  --hash=sha256:9a09d59bef1db94f384b5bcc2d523694d338f3df6b757aeeaf7baca5d0c0be88 \
31
34
  --hash=sha256:a5876d0a60355af98d535c47f6cd6eb0f8a432396dab26845d380b92f8412422 \
32
35
  --hash=sha256:a6a4b98a9132e0f45c9fc245a63894cfd8c45fb7a0d6bffc5eab3ec327cf7324 \
36
+ --hash=sha256:a6b4ff33f7e011bbaa148238d131c4fd4f8afbab3c104ddfbdb2b12b74ff7016 \
33
37
  --hash=sha256:b7d501d5eb5d4f67207df364752ad697465b834268744be7581c18d81d35d41d \
34
38
  --hash=sha256:c59acfa8eb73a1e0d484392dc002bdf001fd4ce73394e0132df3d1ab6093d7cb \
35
39
  --hash=sha256:c75116c9de79949de23006e2d9b35ee82874c594fcf5c0311b439acaa14b8441 \
40
+ --hash=sha256:dad3d233d441a022c1f7155f0fb9d5aff7b97c1ea8c7dfa02cce586b16ab2d0b \
36
41
  --hash=sha256:e85880b538e59a59f55117b81f208a6660ad5ac328aad9305f812d9b8bc67a0f \
37
42
  --hash=sha256:eed88b64a5e5da72d6a71cdc5aaeefaa5ced9b748f8d19f89800b339961dad39 \
43
+ --hash=sha256:f8f0bd690e1a41294ac87905e8121c81a3761ec2583c768f13467428606c8c7a \
38
44
  --hash=sha256:f96f0e30b5a95c7631b12bfe214cbc90ec8fe8cfa36920596c10514a65743519 \
39
45
  --hash=sha256:f98e8215e172f567ce80eeaed9107fb4d32b6c44f26983d9b8334658136a205a
40
46
 
@@ -54,23 +60,29 @@ certifi==2026.7.22 \
54
60
  charset-normalizer==3.5.1 \
55
61
  --hash=sha256:15f024313246a4ed976c60f440bb8d257815513a681d212ff74fd46f7d715a90 \
56
62
  --hash=sha256:2f06b7eae9dbe77fe1d644ca244dad508de8d302870a43f3c559b521270938a0 \
63
+ --hash=sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2 \
57
64
  --hash=sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2 \
58
65
  --hash=sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa \
59
66
  --hash=sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f \
60
67
  --hash=sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3 \
61
68
  --hash=sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6 \
69
+ --hash=sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959 \
62
70
  --hash=sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a \
63
71
  --hash=sha256:96eefc178f8636b9c760c5829345307fd81cfae9ab1e80997dbddeb0f54ee9a3 \
64
72
  --hash=sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99 \
65
73
  --hash=sha256:aa2bb0b37202dca27175591f761108b5d34096ade1191ffe4808bdf6b1571488 \
74
+ --hash=sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6 \
66
75
  --hash=sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08 \
76
+ --hash=sha256:be47f99644b208bff7766314013f9acf57b056b04191d570d68ad14022cf5b1d \
67
77
  --hash=sha256:c428c6c31eb5f4277d7f8eccaf767fbd548ddd5ce3c8b4f4cbbfab3d96b5904c \
78
+ --hash=sha256:c658c50ac0c98cd755a2dd50b7977d3bca7df401dcc47fbdfa87db53ef7d4e8b \
68
79
  --hash=sha256:c7b742bf31c88566b4bb6335a7f393bb322e580b6bb98df7bd0c25e6e3519ce8 \
69
80
  --hash=sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2 \
70
81
  --hash=sha256:d1ee1e296209fdce05b81b663250eefa02213a2da7b41bf26f7829b8ba3545aa \
71
82
  --hash=sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d \
72
83
  --hash=sha256:e9fbdce1e47394b09bc9f26ab117dfc8d6491977a11d86f592bb42c779db2fda \
73
- --hash=sha256:eda059b6bc8bc0812d626fd91a7ce01bf583df0a61296eff390fd94141a34e30
84
+ --hash=sha256:eda059b6bc8bc0812d626fd91a7ce01bf583df0a61296eff390fd94141a34e30 \
85
+ --hash=sha256:f9b1e28d0e8dbfa858abdba91d6b547beaf2df1a59bec6da6faae7b96a4991a9
74
86
 
75
87
  idna==3.20 \
76
88
  --hash=sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c
@@ -82,13 +94,17 @@ urllib3==2.8.0 \
82
94
  pillow==12.3.0 \
83
95
  --hash=sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756 \
84
96
  --hash=sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3 \
97
+ --hash=sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8 \
85
98
  --hash=sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd \
86
99
  --hash=sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b \
100
+ --hash=sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb \
87
101
  --hash=sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6 \
88
102
  --hash=sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f \
89
103
  --hash=sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a \
90
104
  --hash=sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91 \
91
105
  --hash=sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66 \
106
+ --hash=sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a \
107
+ --hash=sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09 \
92
108
  --hash=sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965 \
93
109
  --hash=sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8 \
94
110
  --hash=sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd \
@@ -99,6 +115,7 @@ pillow==12.3.0 \
99
115
  --hash=sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321 \
100
116
  --hash=sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec \
101
117
  --hash=sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65 \
118
+ --hash=sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d \
102
119
  --hash=sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7
103
120
 
104
121
  # Windows only: Python there has no time zone database (docs/brain/DESKTOP.md D3).
@@ -171,6 +171,7 @@ export function columnSvg(columns, { label, max = Math.max(1, ...columns.map((c)
171
171
  const step = 10;
172
172
  const barW = 7;
173
173
  const bars = columns.map((c, i) => {
174
+ if (!c.parts.length) return ''; // a gap
174
175
  let below = 0;
175
176
  const x = round(i * step + (step - barW) / 2);
176
177
  const rects = c.parts.filter((p) => p.value > 0).map((p) => {
@@ -179,7 +180,9 @@ export function columnSvg(columns, { label, max = Math.max(1, ...columns.map((c)
179
180
  below += p.value;
180
181
  return `<rect class="${p.cls}" x="${x}" y="${round(top)}" width="${barW}" height="${round(bottom - top)}"/>`;
181
182
  }).join('');
182
- return `<g><title>${escapeHtml(c.title)}</title>${rects}</g>`;
183
+ // The whole column's height answers a click, not just its bar, so a short one is easy to hit.
184
+ const bar = `<g><title>${escapeHtml(c.title)}</title><rect class="hit" x="${round(i * step)}" y="0" width="${step}" height="${H}"/>${rects}</g>`;
185
+ return c.href ? `<a href="${escapeHtml(c.href)}">${bar}</a>` : bar;
183
186
  }).join('');
184
187
  return `<svg class="bars" viewBox="0 0 ${columns.length * step} ${H}" preserveAspectRatio="none" role="img" aria-label="${escapeHtml(label)}">${bars}</svg>`;
185
188
  }
@@ -188,23 +191,66 @@ function labelRow(labels) {
188
191
  return `<ol class="bar-labels" aria-hidden="true">${labels.map((l) => `<li>${l ? escapeHtml(l) : ''}</li>`).join('')}</ol>`;
189
192
  }
190
193
 
191
- /** Every full release, oldest to newest, by how many changes of each kind. */
192
- export function releaseChartHtml(full) {
194
+ /** A version's id on /releases/ (and in its links). */
195
+ export const anchor = (version) => `v${version}`;
196
+ const STAGES = ['Alpha', 'Beta', 'Preview'];
197
+ const shortDate = (d) => longDate(d).replace(/ (\w{3})\w* \d{4}$/, ' $1');
198
+
199
+ /**
200
+ * Every full release, oldest to newest, by how many changes of each kind; and
201
+ * before them the milestones before 0.1 (`before`, newest first, as on the
202
+ * page), by stage and by the commits that landed for each, since there was no
203
+ * changelog yet (Blake, 2026-10-03: the chart began at "a really stacked 0.1.0",
204
+ * as if the work had started there). A gap marks where the releases begin, and
205
+ * a band under the bars names each stretch. Each bar opens its notes.
206
+ */
207
+ export function releaseChartHtml(full, before = []) {
193
208
  const oldestFirst = [...full].reverse();
194
- const columns = oldestFirst.map((r) => {
209
+ const earlier = [...before].reverse();
210
+ const releases = oldestFirst.map((r) => {
195
211
  const c = changeCounts(r);
196
212
  const total = KINDS.reduce((s, k) => s + (c[k] || 0), 0);
197
213
  const parts = KINDS.map((k) => ({ value: c[k] || 0, cls: `k-${k}` }));
198
214
  const said = KINDS.filter((k) => c[k]).map((k) => `${c[k]} ${k}`).join(', ');
199
- return { parts, title: `${r.version} · ${longDate(r.date)}: ${total} changes (${said})` };
215
+ return { parts, title: `${r.version} · ${longDate(r.date)}: ${total} changes (${said})`, href: `#${anchor(r.version)}` };
200
216
  });
217
+ const milestones = earlier.map((b) => ({
218
+ parts: [{ value: b.commits, cls: `k-${b.stage.toLowerCase()}` }],
219
+ title: `${b.title} · ${longDate(b.date)}: ${b.commits} commits (before the changelog began)`,
220
+ href: `#${b.id}`,
221
+ }));
222
+ const gap = milestones.length ? [{ parts: [], title: '' }] : [];
223
+ const columns = [...milestones, ...gap, ...releases];
201
224
  const total = full.reduce((s, r) => s + Object.values(changeCounts(r)).reduce((a, b) => a + b, 0), 0);
225
+ const commits = earlier.reduce((s, b) => s + b.commits, 0);
202
226
  // The first and the newest only: at phone width the columns are too narrow to name more.
203
- const labels = oldestFirst.map((r, i) => (i === 0 || i === oldestFirst.length - 1 ? `${r.version} · ${longDate(r.date).replace(/ (\w{3})\w* \d{4}$/, ' $1')}` : ''));
227
+ const firstLabel = earlier.length ? `${earlier[0].title.split(':')[0]} · ${shortDate(earlier[0].date)}` : `${oldestFirst[0].version} · ${shortDate(oldestFirst[0].date)}`;
228
+ const labels = columns.map((c, i) => (i === 0 ? firstLabel : i === columns.length - 1 ? `${full[0].version} · ${shortDate(full[0].date)}` : ''));
204
229
  const svg = columnSvg(columns, {
205
- label: `Changes per release, ${oldestFirst.length} releases from ${oldestFirst[0].version} to ${full[0].version}, ${total} in all`,
230
+ label: (earlier.length
231
+ ? `${earlier.length} milestones before 0.1, from ${longDate(earlier[0].date)}, by commits (${commits} in all); then changes per release, `
232
+ : 'Changes per release, ')
233
+ + `${oldestFirst.length} releases from ${oldestFirst[0].version} to ${full[0].version}, ${total} in all`,
206
234
  });
207
- return `\n ${svg}\n ${labelRow(labels)}\n `;
235
+ // The band: each stage, then the releases, as wide as their columns.
236
+ const band = earlier.length ? stageBand([
237
+ ...STAGES.map((stage) => {
238
+ const of = earlier.filter((b) => b.stage === stage);
239
+ return of.length && { n: of.length, cls: `s-${stage.toLowerCase()}`, text: stage, short: stage[0], title: `${stage}: ${of.length} milestones, ${shortDate(of[0].date)} to ${shortDate(of.at(-1).date)}` };
240
+ }).filter(Boolean),
241
+ { n: 1 },
242
+ { n: releases.length, cls: 's-release', text: 'Releases', short: 'Releases', title: `${releases.length} releases, from ${shortDate(oldestFirst[0].date)}` },
243
+ ]) : '';
244
+ return `\n ${svg}\n ${band}${labelRow(labels)}\n `;
245
+ }
246
+
247
+ // One cell per column, as the labels row has, since the site's content policy
248
+ // allows no inline styles to size a cell per stretch: the stretch's name sits
249
+ // in its first cell and runs on into the rest.
250
+ function stageBand(groups) {
251
+ const cells = groups.flatMap((g) => Array.from({ length: g.n }, (_, i) => (!g.cls ? '<li></li>'
252
+ : `<li class="${g.cls}${i === 0 ? ' first' : ''}"${i === 0 ? ` title="${escapeHtml(g.title)}"` : ''}>${i === 0 ? `<span class="long">${escapeHtml(g.text)}</span><span class="short">${escapeHtml(g.short)}</span>` : ''}</li>`)));
253
+ return `<ol class="stage-band" aria-hidden="true">${cells.join('')}</ol>\n `;
208
254
  }
209
255
 
210
256
  /** The sample network's shape: who's two steps away, and through how many of your connections. */
@@ -288,7 +334,6 @@ export function generate({ changelog, testCount, sample }) {
288
334
  'change-count': thousands(full.reduce((sum, r) => sum + Object.values(changeCounts(r)).reduce((a, b) => a + b, 0), 0)),
289
335
  'test-count': thousands(testCount),
290
336
  news: newsHtml(full),
291
- 'release-chart': releaseChartHtml(full),
292
337
  'sample-connections': thousands(s.connections),
293
338
  'sample-degree2': thousands(s.degree2),
294
339
  'sample-bridges': String(s.bridges),
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env node
2
+ // CI's check of the Windows and Linux apps as installed (docs/brain/DESKTOP.md
3
+ // rule 6), the same checks release.yml runs on the Mac app: it opens, every
4
+ // page answers, the scanner uses the Python inside the app, Settings → Updates
5
+ // offers the installer and never the Mac's swap, a second copy hands over and
6
+ // exits, it quits, and nothing of it is left running.
7
+ //
8
+ // node scripts/smoke-desktop.mjs --exe <app executable> --dir <install folder> --home <scratch data folder> [--shot <png>]
9
+ //
10
+ // Node, not PowerShell: PowerShell can't pass an environment variable set to
11
+ // '' (it deletes it), and Electron's stdout is hard to read on Windows, so the
12
+ // app writes its address to a file (SIX_DEGREES_SMOKE_READY).
13
+
14
+ import { spawn, execFileSync } from 'node:child_process';
15
+ import { existsSync, readFileSync, readdirSync, readlinkSync, mkdirSync, rmSync } from 'node:fs';
16
+ import net from 'node:net';
17
+ import path from 'node:path';
18
+ import { startTestReleaseServer } from './test-release-server.mjs';
19
+
20
+ const arg = (name) => { const i = process.argv.indexOf(`--${name}`); return i > -1 ? process.argv[i + 1] : null; };
21
+ const EXE = arg('exe');
22
+ const DIR = arg('dir');
23
+ const HOME = arg('home');
24
+ const SHOT = arg('shot');
25
+ if (!EXE || !DIR || !HOME) { console.error('Usage: --exe <app> --dir <install folder> --home <data folder> [--shot <png>]'); process.exit(2); }
26
+ const WIN = process.platform === 'win32';
27
+ const KIND = WIN ? 'windows-app' : 'linux-app';
28
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
29
+ const fail = (msg) => { console.error(`::error::${msg}`); process.exit(1); };
30
+ const same = (a, b) => (WIN ? a.toLowerCase() === b.toLowerCase() : a === b);
31
+ const inside = (file, dir) => {
32
+ const f = path.resolve(file); const d = path.resolve(dir) + path.sep;
33
+ return WIN ? f.toLowerCase().startsWith(d.toLowerCase()) : f.startsWith(d);
34
+ };
35
+
36
+ const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
37
+ const slug = String(pkg.repository?.url || pkg.repository).match(/github\.com[/:]([^/]+\/[^/.#?]+)/)[1];
38
+ // A pretend newer release, so Updates has something to offer (and must not install).
39
+ const releases = await startTestReleaseServer({ slug, release: { version: '99.0.0', assets: [{ name: 'Six-Degrees-99.0.0-win-x64-Setup.exe', data: Buffer.from('not an installer') }] } });
40
+
41
+ rmSync(HOME, { recursive: true, force: true });
42
+ mkdirSync(HOME, { recursive: true });
43
+ const READY = path.join(HOME, '..', `ready-${process.pid}.txt`);
44
+
45
+ function start(extraEnv = {}) {
46
+ rmSync(READY, { force: true });
47
+ const env = {
48
+ ...process.env,
49
+ SIX_DEGREES_HOME: HOME,
50
+ SIX_DEGREES_SMOKE: '1',
51
+ SIX_DEGREES_SMOKE_READY: READY,
52
+ SIX_DEGREES_TEST_RELEASES: releases.origin,
53
+ ...(SHOT ? { SIX_DEGREES_SMOKE_SHOT: SHOT } : {}),
54
+ ...extraEnv,
55
+ };
56
+ const child = spawn(EXE, [], { env, stdio: 'ignore', detached: !WIN });
57
+ child.on('error', (e) => fail(`Could not start the app: ${e.message}`));
58
+ return child;
59
+ }
60
+ async function ready(child) {
61
+ for (let i = 0; i < 150; i++) {
62
+ if (existsSync(READY)) {
63
+ const url = readFileSync(READY, 'utf8').trim();
64
+ if (url) return url;
65
+ }
66
+ if (child.exitCode !== null) fail(`The app exited while starting (code ${child.exitCode}).`);
67
+ await sleep(1000);
68
+ }
69
+ fail('The app never became ready.');
70
+ }
71
+ const get = async (url) => { const r = await fetch(url); return { status: r.status, body: await r.text() }; };
72
+ const post = async (url, body) => {
73
+ const r = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
74
+ return { status: r.status, json: await r.json().catch(() => ({})) };
75
+ };
76
+ const listening = (port) => new Promise((resolve) => {
77
+ const s = net.connect({ host: '127.0.0.1', port }, () => { s.destroy(); resolve(true); });
78
+ s.on('error', () => resolve(false));
79
+ });
80
+ // Every process running a program from inside the install folder.
81
+ function appProcesses() {
82
+ if (WIN) {
83
+ const out = execFileSync('powershell', ['-NoProfile', '-Command',
84
+ 'Get-CimInstance Win32_Process | Select-Object ProcessId,ExecutablePath | ConvertTo-Json -Compress'], { maxBuffer: 64 << 20 }).toString();
85
+ const list = JSON.parse(out || '[]');
86
+ return (Array.isArray(list) ? list : [list]).filter((p) => p.ExecutablePath && inside(p.ExecutablePath, DIR)).map((p) => `${p.ProcessId} ${p.ExecutablePath}`);
87
+ }
88
+ const found = [];
89
+ for (const pid of readdirSync('/proc').filter((n) => /^\d+$/.test(n))) {
90
+ try { const exe = readlinkSync(`/proc/${pid}/exe`); if (inside(exe, DIR)) found.push(`${pid} ${exe}`); } catch { /* gone, or not ours to read */ }
91
+ }
92
+ return found;
93
+ }
94
+ async function quit(child) {
95
+ if (WIN) {
96
+ // As a window's close button or Setup does: taskkill without /F sends WM_CLOSE.
97
+ try { execFileSync('taskkill', ['/PID', String(child.pid)], { stdio: 'ignore' }); } catch { /* reported below if it stays */ }
98
+ } else {
99
+ process.kill(child.pid, 'SIGTERM');
100
+ }
101
+ for (let i = 0; i < 45 && child.exitCode === null; i++) await sleep(1000);
102
+ if (child.exitCode === null) fail('The app did not quit.');
103
+ await sleep(3000);
104
+ }
105
+
106
+ // ---- open it, reach every page ------------------------------------------------
107
+ let app = start();
108
+ let url = await ready(app);
109
+ console.log(`Running at ${url}`);
110
+ for (const p of ['', 'paths', 'queue', 'setup', 'settings', 'api/network', 'api/company-scores', 'api/scraper', 'api/settings']) {
111
+ const { status } = await get(`${url}/${p}`);
112
+ console.log(` /${p} ${status}`);
113
+ if (status !== 200) fail(`/${p} answered ${status}`);
114
+ }
115
+
116
+ // ---- the scanner runs on the app's own Python ------------------------------------
117
+ const scanner = JSON.parse((await get(`${url}/api/scraper`)).body).checks;
118
+ console.log(' scanner:', scanner.pythonSource, scanner.pythonVersion, scanner.pythonPath, '· chrome', scanner.chrome);
119
+ if (scanner.pythonSource !== 'bundled' || !scanner.dependencies || !inside(scanner.pythonPath, DIR)) fail('The scanner isn\'t using the Python inside the app.');
120
+ if (!scanner.chrome) fail('Google Chrome is installed on this runner, and the app didn\'t find it.');
121
+
122
+ // ---- Updates: the installer, never the Mac's swap or a Terminal line ---------------
123
+ const local = JSON.parse((await get(`${url}/api/update`)).body);
124
+ console.log(' updates:', local.kind, local.version, local.command);
125
+ if (local.kind !== KIND || local.command !== null) fail(`Updates thinks this is ${local.kind} (command ${local.command}).`);
126
+ const checked = await post(`${url}/api/update`, { action: 'check-release' });
127
+ console.log(' check:', checked.status, checked.json.newer, checked.json.install, checked.json.fallback);
128
+ if (checked.status !== 200 || !checked.json.newer || checked.json.install || checked.json.fallback) fail('Check for updates didn\'t offer the newer release the right way.');
129
+ const refused = await post(`${url}/api/update`, { action: 'install-release' });
130
+ if (refused.status !== 400) fail(`Install and restart answered ${refused.status}: it must refuse off the Mac.`);
131
+ if (releases.requests.some((r) => r.includes('/releases/download/'))) fail('Something downloaded the pretend release.');
132
+
133
+ // ---- a second copy hands over and exits --------------------------------------------
134
+ const second = spawn(EXE, [], { env: { ...process.env, SIX_DEGREES_HOME: HOME }, stdio: 'ignore' });
135
+ for (let i = 0; i < 20 && second.exitCode === null; i++) await sleep(1000);
136
+ if (second.exitCode === null) { try { second.kill(); } catch {} fail('A second copy kept running.'); }
137
+ console.log(' a second launch handed over and exited');
138
+
139
+ // ---- quit; nothing left ---------------------------------------------------------------
140
+ const port = Number(new URL(url).port);
141
+ await quit(app);
142
+ if (await listening(port)) fail('The server was left running.');
143
+ const left = appProcesses();
144
+ if (left.length) fail(`App processes were left running:\n${left.join('\n')}`);
145
+ const pyRoot = path.join(DIR, 'resources', 'python');
146
+ const bytecode = (function find(dir) {
147
+ for (const e of readdirSync(dir, { withFileTypes: true })) {
148
+ if (!e.isDirectory()) continue;
149
+ if (e.name === '__pycache__') return path.join(dir, e.name);
150
+ const deeper = find(path.join(dir, e.name)); if (deeper) return deeper;
151
+ }
152
+ return null;
153
+ })(pyRoot);
154
+ if (bytecode) fail(`Something wrote bytecode into the app's Python: ${bytecode}`);
155
+ console.log(' quit, and nothing was left running');
156
+
157
+ // ---- again with the app's Python off: a job to stop while quitting ------------------------
158
+ app = start({ SIX_DEGREES_PYTHON: '' });
159
+ url = await ready(app);
160
+ const checks = JSON.parse((await get(`${url}/api/scraper`)).body).checks;
161
+ const action = checks.installFrom ? 'install' : checks.download ? 'setup' : null;
162
+ if (action) {
163
+ const started = await post(`${url}/api/scraper`, { action });
164
+ console.log(` started "${action}" to have a job running:`, started.status);
165
+ await sleep(3000);
166
+ } else {
167
+ console.log(' no Python on this runner to install from: quitting without a job');
168
+ }
169
+ await quit(app);
170
+ if (await listening(Number(new URL(url).port))) fail('The server was left running (second run).');
171
+ const left2 = appProcesses();
172
+ if (left2.length) fail(`App processes were left running after quitting mid-job:\n${left2.join('\n')}`);
173
+
174
+ await releases.close();
175
+ rmSync(READY, { force: true });
176
+ console.log('\n✓ Opened, reached every page, scanner on its own Python, updates by installer, refused a second copy, quit, left nothing.');
177
+ process.exit(0);
@@ -0,0 +1,100 @@
1
+ ; Six Degrees for Windows: a one-click, per-user Setup.exe (docs/brain/DESKTOP.md D3).
2
+ ; Built by scripts/build-desktop.mjs --platform=win32, which passes the /D values:
3
+ ; AppVersion, NumericVersion (x.y.z.0), SourceDir (the packaged app), OutputDir, IconFile.
4
+ ;
5
+ ; Installs for this user only (no admin prompt) into
6
+ ; %LOCALAPPDATA%\Programs\Six Degrees, adds Start Menu and Desktop shortcuts,
7
+ ; and opens the app. Running a newer Setup.exe over it updates it: it closes a
8
+ ; running copy the way Exit does (the scan stops cleanly, its Chrome closes),
9
+ ; replaces the app and opens it again. The network in %USERPROFILE%\.six-degrees
10
+ ; is never touched, by installing, updating or uninstalling (DESKTOP.md rule 3).
11
+
12
+ #ifndef AppVersion
13
+ #define AppVersion "0.0.0"
14
+ #endif
15
+ #ifndef NumericVersion
16
+ #define NumericVersion "0.0.0.0"
17
+ #endif
18
+
19
+ [Setup]
20
+ ; Fixed for good: Windows knows the installed app by it, so a newer Setup.exe updates it.
21
+ AppId={{010929DA-B1D1-47A6-B6C8-BE7CC9ACA2C5}
22
+ AppName=Six Degrees
23
+ AppVersion={#AppVersion}
24
+ AppVerName=Six Degrees {#AppVersion}
25
+ VersionInfoVersion={#NumericVersion}
26
+ AppPublisher=Six Degrees
27
+ AppPublisherURL=https://sixdegreesapp.com
28
+ AppSupportURL=https://github.com/blakeb056/six-degrees
29
+ DefaultDirName={localappdata}\Programs\Six Degrees
30
+ PrivilegesRequired=lowest
31
+ DisableWelcomePage=yes
32
+ DisableDirPage=yes
33
+ DisableProgramGroupPage=yes
34
+ DisableReadyPage=yes
35
+ DisableFinishedPage=yes
36
+ ArchitecturesAllowed=x64compatible
37
+ ArchitecturesInstallIn64BitMode=x64compatible
38
+ ; Windows 10 1809 or later (Electron's own minimum is Windows 10).
39
+ MinVersion=10.0.17763
40
+ OutputDir={#OutputDir}
41
+ OutputBaseFilename=Six-Degrees-{#AppVersion}-win-x64-Setup
42
+ SetupIconFile={#IconFile}
43
+ UninstallDisplayIcon={app}\Six Degrees.exe
44
+ UninstallDisplayName=Six Degrees
45
+ Compression=lzma2/max
46
+ SolidCompression=yes
47
+ LZMAUseSeparateProcess=yes
48
+ CloseApplications=yes
49
+ RestartApplications=no
50
+ UsedUserAreasWarning=no
51
+ SetupLogging=yes
52
+ WizardStyle=modern
53
+
54
+ [InstallDelete]
55
+ ; The previous version's files, so nothing of it lingers beside the new one. Only
56
+ ; the app's own folder: never the data folder.
57
+ Type: filesandordirs; Name: "{app}\resources"
58
+ Type: filesandordirs; Name: "{app}\locales"
59
+
60
+ [Files]
61
+ Source: "{#SourceDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
62
+
63
+ [Icons]
64
+ Name: "{autoprograms}\Six Degrees"; Filename: "{app}\Six Degrees.exe"; AppUserModelID: "com.blakeburford.sixdegrees"
65
+ Name: "{autodesktop}\Six Degrees"; Filename: "{app}\Six Degrees.exe"; AppUserModelID: "com.blakeburford.sixdegrees"
66
+
67
+ [Run]
68
+ ; One click: a progress bar, then the app opens (not when installed silently, as CI does).
69
+ Filename: "{app}\Six Degrees.exe"; Flags: nowait skipifsilent
70
+
71
+ [UninstallRun]
72
+ Filename: "{sys}\taskkill.exe"; Parameters: "/IM ""Six Degrees.exe"""; Flags: runhidden; RunOnceId: "CloseSixDegrees"
73
+
74
+ [Code]
75
+ // Is Six Degrees running for this user? tasklist's list, searched by find: 0 when it is.
76
+ function SixDegreesRunning(): Boolean;
77
+ var
78
+ Code: Integer;
79
+ begin
80
+ Result := Exec(ExpandConstant('{cmd}'), '/C tasklist /FI "IMAGENAME eq Six Degrees.exe" /FI "USERNAME eq %USERNAME%" | find /I "Six Degrees.exe" >NUL',
81
+ '', SW_HIDE, ewWaitUntilTerminated, Code) and (Code = 0);
82
+ end;
83
+
84
+ // Before replacing a running copy: ask it to close, as Exit does (taskkill
85
+ // without /F sends its window WM_CLOSE: the app stops a scan cleanly, then its
86
+ // server), and wait up to 30 seconds for it to go.
87
+ function PrepareToInstall(var NeedsRestart: Boolean): String;
88
+ var
89
+ Code, I: Integer;
90
+ begin
91
+ Result := '';
92
+ if not SixDegreesRunning() then Exit;
93
+ Exec(ExpandConstant('{sys}\taskkill.exe'), '/IM "Six Degrees.exe"', '', SW_HIDE, ewWaitUntilTerminated, Code);
94
+ for I := 1 to 60 do
95
+ begin
96
+ if not SixDegreesRunning() then Exit;
97
+ Sleep(500);
98
+ end;
99
+ Result := 'Six Degrees is still open. Close it (File, then Exit), then run Setup again.';
100
+ end;
@@ -0,0 +1,22 @@
1
+ (globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,68863,e=>{"use strict";var n=e.i(43476),t=e.i(71645),i=e.i(18566),o=e.i(2776),s=e.i(22016),a=e.i(40996);let r={install:"Install",setup:"Set up the scanner"};var l=e.i(64745),d=e.i(48235),c=e.i(51798);let h=`
2
+ @keyframes radarSweep { to { transform: rotate(360deg); } }
3
+ @keyframes radarPing { 0% { opacity: .9; r: 3; } 100% { opacity: 0; r: 11; } }
4
+ .radar-sweep { transform-origin: 118px 118px; animation: radarSweep 2.6s linear infinite; }
5
+ .radar-ping { animation: radarPing 2.6s ease-out infinite; }
6
+ .radar-go { transition: transform .15s ease, box-shadow .2s ease; }
7
+ .radar-go:not(:disabled):hover { transform: scale(1.04); box-shadow: 0 0 34px rgba(0,255,136,.45); }
8
+ .radar-go:not(:disabled):active { transform: scale(.97); }
9
+ @media (prefers-reduced-motion: reduce) { .radar-sweep, .radar-ping { animation: none; } }
10
+ `,p=[[.62,.7],[.38,2.1],[.8,3.3],[.5,4.4],[.72,5.6]];function g(e,n,t){let i=n=>`${(118+e*Math.cos(n)).toFixed(2)} ${(118+e*Math.sin(n)).toFixed(2)}`,o=+(t-n>Math.PI);return`M${i(n)} A${e} ${e} 0 ${o} 1 ${i(t)}`}function u({li:e,running:t,scanning:i,disabled:o,label:s,sublabel:a,onScan:r,onPace:l}){let d=e?.limits?.pace||"fast",f=e?.limits?.daily||0,x=e?.unreadable?f:e?.searchesToday||0,y=f?Math.max(0,f-x):null,b=f?Math.min(1,x/f):0,m=b>=.9?"#ff6b6b":b>=.6?"#FFD700":"#00ff88",j=-Math.PI/2,w=108;return(0,n.jsxs)("div",{style:{display:"flex",gap:22,alignItems:"center",flexWrap:"wrap",padding:"14px 16px",borderRadius:14,border:"1px solid rgba(0,255,136,0.18)",background:"radial-gradient(circle at 20% 50%, rgba(0,255,136,0.06), rgba(255,255,255,0.02) 60%)"},children:[(0,n.jsx)("style",{children:h}),(0,n.jsxs)("div",{style:{position:"relative",width:236,height:236,flexShrink:0},children:[(0,n.jsxs)("svg",{width:236,height:236,"aria-hidden":"true",style:{position:"absolute",inset:0},children:[(0,n.jsxs)("defs",{children:[(0,n.jsxs)("radialGradient",{id:"radarBg",children:[(0,n.jsx)("stop",{offset:"0",stopColor:"#0f2a22"}),(0,n.jsx)("stop",{offset:"1",stopColor:"#0a0a1a"})]}),(0,n.jsxs)("linearGradient",{id:"radarBeam",x1:"0",y1:"0",x2:"1",y2:"0",children:[(0,n.jsx)("stop",{offset:"0",stopColor:"#00ff88",stopOpacity:"0"}),(0,n.jsx)("stop",{offset:"1",stopColor:"#00ff88",stopOpacity:"0.45"})]})]}),(0,n.jsx)("circle",{cx:118,cy:118,r:w,fill:"url(#radarBg)",stroke:"rgba(0,255,136,0.25)"}),[.33,.66].map(e=>(0,n.jsx)("circle",{cx:118,cy:118,r:w*e,fill:"none",stroke:"rgba(0,255,136,0.14)",strokeDasharray:"2 5"},e)),(0,n.jsx)("line",{x1:118-w,y1:118,x2:118+w,y2:118,stroke:"rgba(0,255,136,0.1)"}),(0,n.jsx)("line",{x1:118,y1:118-w,x2:118,y2:118+w,stroke:"rgba(0,255,136,0.1)"}),(0,n.jsxs)("g",{className:t?"radar-sweep":void 0,opacity:t?1:.35,children:[(0,n.jsx)("path",{d:`M118 118 L${118+w} 118 A${w} ${w} 0 0 0 ${118+.8525245220595057*w} ${118+-.5226872289306592*w} Z`,fill:"url(#radarBeam)"}),(0,n.jsx)("line",{x1:118,y1:118,x2:118+w,y2:118,stroke:"#00ff88",strokeOpacity:.8,strokeWidth:1.5})]}),p.map(([e,i],o)=>(0,n.jsxs)("g",{children:[(0,n.jsx)("circle",{cx:118+w*e*Math.cos(i),cy:118+w*e*Math.sin(i),r:2.4,fill:"#00ff88",opacity:t?.9:.35}),t&&(0,n.jsx)("circle",{className:"radar-ping",style:{animationDelay:`${i/(2*Math.PI)*2.6}s`},cx:118+w*e*Math.cos(i),cy:118+w*e*Math.sin(i),r:3,fill:"none",stroke:"#00ff88"})]},o)),(0,n.jsx)("path",{d:g(w+5,j,j+2*Math.PI-.001),fill:"none",stroke:"rgba(255,255,255,0.08)",strokeWidth:5}),b>0&&(0,n.jsx)("path",{d:g(w+5,j,j+2*Math.PI*b-.001),fill:"none",stroke:m,strokeWidth:5,strokeLinecap:"round"})]}),(0,n.jsxs)("button",{type:"button",className:"radar-go",onClick:r,disabled:o,style:{position:"absolute",left:56,top:56,width:124,height:124,borderRadius:"50%",cursor:o?"not-allowed":"pointer",border:`2px solid ${o?"rgba(255,255,255,0.12)":"#00ff88"}`,background:o?"rgba(20,24,40,0.92)":"radial-gradient(circle at 50% 35%, #134d3a, #0b2a22 70%)",color:o?"#667":"#eafff5",boxShadow:o?"none":"0 0 22px rgba(0,255,136,0.25)",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:2,padding:8},children:[(0,n.jsx)("span",{style:{fontSize:22,fontWeight:900,letterSpacing:2},children:i?"•••":"SCAN"}),(0,n.jsx)("span",{style:{fontSize:11,fontWeight:700,opacity:.85,lineHeight:1.25,textAlign:"center"},children:s}),a&&(0,n.jsx)("span",{style:{fontSize:10,opacity:.6,textAlign:"center"},children:a})]})]}),(0,n.jsxs)("div",{style:{flex:1,minWidth:260},children:[(0,n.jsx)("div",{style:{fontSize:10,fontWeight:800,letterSpacing:1,color:"#6b7090",textTransform:"uppercase",marginBottom:8},children:"Speed"}),(0,n.jsx)("div",{role:"radiogroup","aria-label":"Scan speed",style:{display:"grid",gridTemplateColumns:"repeat(3, 1fr)",gap:6},children:c.PACE_NAMES.map(e=>{let i=c.PACES[e],o=d===e,s="slow"===e?"#00E5FF":"medium"===e?"#FFD700":"#FF7043";return(0,n.jsxs)("button",{type:"button",role:"radio","aria-checked":o,disabled:t||!l,onClick:()=>l?.(e),title:`${i.pagePause} s before each page, ${i.chunkCooldown/60} min more after every 10, profiles ${i.profileGap} s apart`,style:{padding:"10px 8px",borderRadius:10,cursor:t?"not-allowed":"pointer",textAlign:"left",border:`1.5px solid ${o?s:"rgba(255,255,255,0.1)"}`,background:o?`${s}1a`:"rgba(255,255,255,0.03)",color:"#ddd"},children:[(0,n.jsx)("div",{style:{fontSize:14,fontWeight:800,color:o?s:"#ccd"},children:i.label}),(0,n.jsxs)("div",{style:{fontSize:11,color:"#8a8fa8",marginTop:2},children:["~",(0,c.searchesPerHour)(e)," searches an hour"]}),(0,n.jsxs)("div",{style:{fontSize:10.5,color:"#6b7090",marginTop:1},children:[i.pagePause," s between pages"]})]},e)})}),(0,n.jsx)("div",{style:{marginTop:10,fontSize:12.5,color:"#b8c4c4",lineHeight:1.6},children:e?null==y?(0,n.jsx)(n.Fragment,{children:"No daily cap is set."}):(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)("b",{style:{color:m},children:y})," of ",f," searches left today",y>0&&(0,n.jsxs)(n.Fragment,{children:[" · at ",(0,c.paceOf)(d).label,", ",(0,c.durationText)((0,c.paceSeconds)(d,y))," to use them"]}),"."]}):(0,n.jsx)(n.Fragment,{children:"Today’s searches show here once the scanner can read them."})}),(0,n.jsx)("div",{style:{marginTop:4,fontSize:11.5,color:"#778",lineHeight:1.55},children:"Fast is how the scanner has always run; Medium and Slow only add waiting, so fewer searches an hour reach LinkedIn. Your daily budget stays the cap at every speed: slower spreads it out, it doesn’t shrink it. Slower lowers the odds of a check from LinkedIn; it can’t promise there won’t be one."})]})]})}var f=e.i(33821);let x={daily:50,monthly:250};var y=e.i(54322);let b="1px solid rgba(255,255,255,0.1)",m=y.TIER_COLORS,j=[25,50,100,200,500],w=[100,250,500,1e3,0],v=[10,25,50,100],k=e=>new Date(e).toLocaleDateString(void 0,{month:"short",day:"numeric"});function S({cooldown:e,onLift:i,disabled:o}){let[s,a]=(0,t.useState)(!1);return e?(0,n.jsxs)("div",{role:"status",style:{padding:"12px 14px",borderRadius:8,fontSize:13,lineHeight:1.6,background:"rgba(255,80,80,0.08)",border:"1px solid rgba(255,80,80,0.35)",color:"#f3c9c9"},children:[(0,n.jsxs)("b",{style:{color:"#ff8080"},children:["Scanning is paused until ",new Date(e.until).toLocaleString(void 0,{weekday:"short",month:"short",day:"numeric",hour:"numeric",minute:"2-digit"}),"."]})," ",e.reason,". Nothing that searches LinkedIn will run until then, so the account can recover.",(0,n.jsx)("div",{style:{marginTop:8,display:"flex",gap:8,alignItems:"center",flexWrap:"wrap"},children:s?(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)("span",{style:{fontSize:12.5},children:"Only if people search works normally for you on linkedin.com right now."}),(0,n.jsx)("button",{onClick:()=>{a(!1),i()},style:{...W,color:"#ff8080"},children:"Yes, lift it"}),(0,n.jsx)("button",{onClick:()=>a(!1),style:W,children:"Keep it"})]}):(0,n.jsx)("button",{onClick:()=>a(!0),disabled:o,style:W,children:"Lift it early…"})})]}):null}function I({used:e,cap:t}){let i=t?Math.min(100,Math.round(e/t*100)):0;return(0,n.jsx)("div",{style:{height:5,borderRadius:3,background:"rgba(255,255,255,0.07)",overflow:"hidden",marginTop:4},children:(0,n.jsx)("div",{style:{height:"100%",width:`${i}%`,background:i>=100?"#ff6b6b":i>=75?"#FFD700":"#3498DB"}})})}function T({li:e,onSetLimits:t,disabled:i}){if(!e)return null;let{limits:o}=e,s=e.unreadable?"?":e.searchesToday,a=e.unreadable?"?":e.profilesToday,r=e=>t({...o,...e}),l=e=>{let n=function(e={},n={}){return Number(n.daily)>100&&Number(n.daily)>Number(e.daily)?`${n.daily} searches a day is more than LinkedIn has put up with. A real account was restricted after 373 searches in 24 hours, run back to back. The default is ${x.daily} a day.
11
+
12
+ Set ${n.daily} a day anyway?`:0===n.monthly&&0!==e.monthly?`With no monthly cap, only the daily budget stops a scan. A real account was restricted after 373 searches in 24 hours with no monthly cap set.
13
+
14
+ Turn the monthly cap off anyway?`:null}(o,{...o,...e});(!n||window.confirm(n))&&r(e)},d=function({daily:e,monthly:n}={}){if(!(({daily:e,monthly:n}={})=>Number(e)>100||0===n)({daily:e,monthly:n}))return null;let t=["A real account was restricted after 373 searches in 24 hours."];return Number(e)>100&&t.push(`${e} a day can use up a free account's month in a day or two.`),0===n&&t.push("With no monthly cap, nothing but the daily budget stops a long run."),t.join(" ")}(o);return(0,n.jsxs)("div",{style:{padding:"12px 14px",borderRadius:8,border:b,background:"rgba(255,255,255,0.03)",fontSize:12.5,color:"#b8c4c4"},children:[(0,n.jsxs)("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(210px, 1fr))",gap:14},children:[(0,n.jsxs)("div",{children:[(0,n.jsxs)("div",{children:[(0,n.jsx)("b",{style:{color:"#fff"},children:s})," of ",o.daily||"no limit"," searches in the last 24 hours"]}),(0,n.jsx)(I,{used:e.searchesToday||0,cap:o.daily})]}),(0,n.jsxs)("div",{children:[(0,n.jsxs)("div",{children:[(0,n.jsx)("b",{style:{color:"#fff"},children:e.searchesMonth})," of ",o.monthly||"no limit"," this month · resets ",k(e.monthResets)]}),(0,n.jsx)(I,{used:e.searchesMonth,cap:o.monthly})]}),(0,n.jsxs)("div",{children:[(0,n.jsxs)("div",{children:[(0,n.jsx)("b",{style:{color:"#fff"},children:a})," of ",o.profiles," profile views today"]}),(0,n.jsx)(I,{used:e.unreadable?o.profiles:e.profilesToday,cap:o.profiles})]})]}),(0,n.jsxs)("div",{style:{display:"flex",gap:8,alignItems:"center",flexWrap:"wrap",marginTop:10},children:[(0,n.jsx)("span",{children:"Budget:"}),(0,n.jsx)("select",{value:o.daily,disabled:i,onChange:e=>l({daily:Number(e.target.value)}),style:R,children:j.map(e=>(0,n.jsxs)("option",{value:e,children:[e," a day"]},e))}),(0,n.jsx)("select",{value:o.monthly,disabled:i,onChange:e=>l({monthly:Number(e.target.value)}),style:R,children:w.map(e=>(0,n.jsx)("option",{value:e,children:e?`${e} a month`:"no monthly cap (Premium)"},e))}),(0,n.jsx)("select",{value:o.profiles,disabled:i,onChange:e=>r({profiles:Number(e.target.value)}),style:R,children:v.map(e=>(0,n.jsxs)("option",{value:e,children:[e," profile views a day"]},e))})]}),(0,n.jsx)("div",{style:{marginTop:8,color:"#778",lineHeight:1.6},children:"Every page of someone’s connections is one search. LinkedIn limits a free account’s people searches by the month (it doesn’t say how many; reports put it around 250–350), resetting on the 1st. When a budget is used, a scan saves what it read and stops; the next one carries on from the same page."}),d&&(0,n.jsxs)("div",{style:{marginTop:8,display:"flex",gap:10,alignItems:"center",flexWrap:"wrap",color:"#FFD700",lineHeight:1.6},children:[(0,n.jsx)("b",{style:{flex:1,minWidth:200},children:d}),(0,n.jsxs)("button",{onClick:()=>r(x),disabled:i,style:{...R,cursor:i?"not-allowed":"pointer",color:"#FFD700",borderColor:"rgba(255,215,0,0.4)"},children:["Back to ",x.daily," a day, ",x.monthly," a month"]})]}),(0,n.jsx)("div",{style:{marginTop:6,color:"#778",lineHeight:1.6},children:"Each circle scan opens the person’s profile once, which is one profile view. Profile views are what LinkedIn restricted an account for, after about 20 in an hour. The scanner opens at most one a minute, and once today’s are used, a scan stops before the next profile and tries that person next time."})]})}function C({paused:e=[],onResume:i,onResumeAll:o,disabled:s}){let[a,r]=(0,t.useState)(!1);if(!e.length)return null;let l=e.filter(e=>e.legacy).length,d=a?e:e.slice(0,8);return(0,n.jsxs)("div",{style:{borderRadius:8,border:b,background:"rgba(255,255,255,0.03)"},children:[(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:10,padding:"10px 14px",borderBottom:b,flexWrap:"wrap"},children:[(0,n.jsxs)("div",{style:{flex:1,minWidth:200},children:[(0,n.jsxs)("b",{style:{fontSize:13.5},children:["Paused — ",e.length," ",1===e.length?"list":"lists"," with more to read"]}),(0,n.jsxs)("div",{style:{fontSize:12,color:"#8b9a9a",marginTop:2},children:["Each carries on from the page it stopped at",l?`; ${l} were read to page 10 before whole lists were read`:"","."]})]}),(0,n.jsx)("button",{onClick:o,disabled:s,style:{...z,opacity:s?.4:1,cursor:s?"not-allowed":"pointer",background:s?"rgba(255,255,255,0.06)":"linear-gradient(135deg, #3498DB, #9B59B6)"},children:"Resume all"})]}),(0,n.jsx)("div",{children:d.map(e=>(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:10,padding:"8px 14px",borderBottom:"1px solid rgba(255,255,255,0.04)"},children:[(0,n.jsx)("span",{style:{width:8,height:8,borderRadius:"50%",background:m[e.tier]||"#667",flexShrink:0}}),(0,n.jsxs)("div",{style:{flex:1,minWidth:0},children:[(0,n.jsx)("div",{style:{fontSize:13,fontWeight:600,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:e.name}),(0,n.jsxs)("div",{style:{fontSize:11.5,color:"#8b9a9a"},children:["Read to page ",e.pagesRead," · carries on at ",e.nextPage,e.at?` \xb7 ${k(e.at)}`:"",e.unclear>=2?" · came back unclear twice, so it waits at the back of Resume all":""]})]}),(0,n.jsx)("button",{onClick:()=>i(e),disabled:s,style:{...z,padding:"6px 12px",fontSize:12,opacity:s?.4:1,cursor:s?"not-allowed":"pointer"},children:"Resume"})]},e.id))}),e.length>8&&(0,n.jsx)("button",{onClick:()=>r(!a),style:{...W,padding:"10px 14px"},children:a?"Show fewer":`Show all ${e.length}`})]})}let z={padding:"8px 14px",borderRadius:7,fontSize:13,fontWeight:700,cursor:"pointer",border:b,background:"rgba(255,255,255,0.08)",color:"#fff"},W={background:"none",border:"none",color:"#8fb8d6",cursor:"pointer",fontSize:12.5,fontWeight:600,padding:0},R={padding:"6px 8px",borderRadius:6,fontSize:12.5,fontWeight:600,background:"rgba(255,255,255,0.08)",color:"#fff",border:b};var $=e.i(8819),P=e.i(98197);e.i(77170);var F=e.i(69083),L=e.i(56899);let B={color:"#3498DB"};function M({onDone:e}){let[i,o]=(0,t.useState)([]),[s,a]=(0,t.useState)(null),[r,l]=(0,t.useState)(null);async function d(){a("pick"),l(null);let n=await (0,L.saveSettings)({sectorFocus:{sectors:i,strength:"lean"},fieldAsked:!0});n.settings?e(n.settings.sectorFocus.sectors):(l(n.error),a(null))}async function c(){a("skip"),l(null),await (0,L.saveSettings)({fieldAsked:!0}),e([])}return(0,n.jsxs)("section",{"aria-labelledby":"field-question","data-field-question":!0,style:{margin:"8px 0 0",padding:"20px 22px 0",borderRadius:12,border:$.LINE,background:"rgba(255,255,255,0.03)"},children:[(0,n.jsx)("div",{style:{fontSize:12,color:"#788",textTransform:"uppercase",letterSpacing:.6},children:"Before you scan · optional"}),(0,n.jsx)("h2",{id:"field-question",style:{fontSize:18,fontWeight:700,margin:"6px 0 4px"},children:"What field are you in?"}),(0,n.jsx)($.Body,{style:{marginTop:0,marginBottom:14},children:"Pick up to three. Companies in your field count for more when your network is scored, so the people there rank higher. You can change it anytime in Settings."}),(0,n.jsx)(P.default,{sectors:i,onChange:o,disabled:!!s}),(0,n.jsxs)("div",{style:{position:"sticky",bottom:0,margin:"16px -22px 0",padding:"12px 22px 16px",borderTop:$.LINE,borderRadius:"0 0 11px 11px",background:"#111121"},children:[(0,n.jsxs)("div",{style:{display:"flex",gap:8,flexWrap:"wrap"},children:[(0,n.jsx)($.Btn,{primary:!0,onClick:d,disabled:!i.length||!!s,children:"pick"===s?"Saving…":"Continue"}),(0,n.jsx)($.Btn,{onClick:c,disabled:!!s,children:"Skip for now"})]}),r&&(0,n.jsx)($.Status,{tone:"bad",children:r})]})]})}function D({sectors:e}){return e.length?(0,n.jsxs)($.Status,{tone:"ok",children:["Your field: ",e.map(e=>(0,F.sectorByKey)(e).label).join(" · "),". Your first scan is scored with it. Change it anytime in ",(0,n.jsx)(s.default,{href:"/settings#sector",style:B,children:"Settings"}),"."]}):(0,n.jsxs)($.Status,{children:["No field for now. You can pick one anytime in ",(0,n.jsx)(s.default,{href:"/settings#sector",style:B,children:"Settings"}),"."]})}let N="1px solid rgba(255,255,255,0.1)",E={photos:"Saving profile photos to this computer",install:"Installing the scanner",setup:"Setting up the scanner",login:"Waiting for you to sign in",full:"Scanning your whole network",refresh:"Checking for new connections","auto-bridge":"Mapping 2nd-degree connections","auto-bridge-retry":"Mapping 2nd degree, hidden ones included",resume:"Carrying on with one paused list","resume-all":"Carrying on with every paused list"};function O(){let e;(0,t.useEffect)(()=>{"updates"===new URLSearchParams(window.location.search).get("check")&&window.location.replace("/settings?check=updates")},[]);let[o,h]=(0,t.useState)(null),[p,g]=(0,t.useState)(!1),[x,y]=(0,t.useState)(10),[b,m]=(0,t.useState)(["S","A"]),[j,w]=Y("six-degrees-bridge-order","newest"),[v,k]=Y("six-degrees-bridge-pages-v2",100),[I,z]=Y("six-degrees-bridge-finish",!0),[W,R]=Y("six-degrees-experimental-auto",!1),[$,P]=Y(a.HIDE_CHROME_KEY,!1),[F,L]=(0,t.useState)(null),B=(0,t.useRef)(null),[O,H]=(0,t.useState)(void 0),[en,et]=(0,t.useState)(null),ei=(0,i.useSearchParams)().get("scan"),[eo,es]=(0,t.useState)(null);(0,t.useEffect)(()=>{if(!ei)return;let e=!0;return(0,a.pickedPerson)(ei).then(n=>{e&&es(n?.person?n:null)},()=>{}),()=>{e=!1}},[ei]);let ea=(0,t.useCallback)(async()=>{try{let e=await fetch("/api/scraper");h(await e.json())}catch{h(e=>e||{ready:!1,checks:{},log:[]})}},[]);async function er(){L(null),await (0,a.stopScrape)().catch(e=>L(e.message)),ea()}async function el(e,n={}){L(null),g(!0);try{let t=await fetch("/api/scraper",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify((0,a.scanRequest)(e,n,$))}),i=await t.json();t.ok||L(i.error||"Could not start.")}catch(e){L(e.message)}finally{g(!1),ea()}}(0,t.useEffect)(()=>{ea();let e=setInterval(ea,1500);return()=>clearInterval(e)},[ea]),(0,t.useEffect)(()=>{if(l.IS_DEMO)return;let e=!1;return fetch("/api/settings").then(e=>e.ok?e.json():null).then(n=>{e||H(n?.settings||null)}).catch(()=>{e||H(null)}),()=>{e=!0}},[]),(0,t.useEffect)(()=>{B.current&&(B.current.scrollTop=B.current.scrollHeight)},[o?.log?.length]);let ed=o?.checks||{},ec=o?.running,eh={...e=function(e){let n=e?.checks||{},t=(n,t,i)=>e?.running&&e.action===n?i:t;if(n.dependencies)return"bundled"===n.pythonSource?{done:!0,text:"Ready. The scanner and its Python come with the app, so there is nothing to install.",button:null}:"custom"===n.pythonSource?{done:!0,text:`Ready, on the Python named in SIX_DEGREES_PYTHON (${n.pythonPath}).`,button:null}:{done:!0,text:"Installed.",button:null};let i=n.ownPython&&"bundled"!==n.ownPython.source?`The Python named in SIX_DEGREES_PYTHON didn't work (${n.ownPython.problem}), so the scanner needs setting up another way. `:"";if(n.installFrom)return{done:!1,text:i+("downloaded"===n.installFrom.source?"Its own Python is already here. One more step, about a minute: it installs the browser-automation packages into it.":"One-time, about a minute. Downloads the browser-automation packages."),button:{action:"install",label:t("install","Install","Installing…")}};if(n.download){let e=Math.round(n.download.size/1048576);return{done:!1,text:`${i}${function(e){if(!e?.version)return"This computer has no Python the scanner can use.";let[n,t]=String(e.version).split(".").map(Number);return 3===n&&t>=10&&t<=14&&!1===e.venv?`This computer's Python ${e.version} can't make the scanner's own environment (on Ubuntu or Debian, python3-venv isn't installed).`:`This computer has Python ${e.version}, and the scanner needs 3.10 to 3.14.`}(n.systemPython)} Set up the scanner downloads its own Python ${n.download.version} (${e} MB, from GitHub) and the scanner's packages (from PyPI) into your data folder. Nothing else on this computer changes. About two minutes.`,button:{action:"setup",label:t("setup","Set up the scanner","Setting up…")}}}return{done:!1,text:`${i}No Python 3.10 to 3.14 was found on this machine. Install one from python.org, then reload.`,button:null}}(o),note:function(e,{done:n,button:t}){if(e?.source!=="bundled")return null;let i=/SIGKILL|code signature|not valid for use in process|quarantine|not permitted/i.test(e.problem||""),o=[`The Python that comes with the app didn't work (${e.problem}).`];return i&&o.push("macOS may have blocked it."),!1===e.retry&&o.push("Six Degrees won't try it again until you restart it."),n?o.push("The scanner uses another Python on this computer instead."):t?o.push(`To scan now, use ${r[t.action]} below: it gives the scanner a Python of its own in your data folder.`):o.push("To scan now, install Python 3.10 to 3.14 from python.org, then reload."),o.join(" ")}(o?.checks?.ownPython,e)};o&&ed.dependencies;let ep=o&&!ed.chrome,eg=o&&!ed.scriptsFound,eu=o?.ready&&!ec,ef=o?.linkedin,ex=!!ef?.cooldown,ey=eu&&!ex,eb=o?.network||{first:0,second:0,third:0},em=eb.first,ej=o&&eh.done?ed.signedIn?0===em?3:4:2:1,ew=(e,n)=>n&&e!==ej?"done":e===ej?"now":e<ej?"done":"next",ev=!l.IS_DEMO&&!en&&function(e,n){if(!e)return null;let t=e.network;return!!t&&!t.first&&!t.second&&!t.third&&!e.running&&(void 0===n?null:!!(n&&!n.fieldAsked&&!n.sectorFocus?.sectors?.length))}(o,O),ek=o&&!ec&&null!=o.exitCode&&0!==o.exitCode,eS=ek?o.failure?.length?o.failure.join("\n"):[...o.log||[]].reverse().find(e=>!/^Stopped \(exit/.test(e)&&!/Warning|warnings\.warn/.test(e)):null;return(0,n.jsxs)("div",{style:{minHeight:"100vh",background:"var(--sd-page)",color:"#fff",fontFamily:"var(--sd-font)"},children:[(0,n.jsx)(f.default,{active:"scan",brand:"span"}),(0,n.jsxs)("div",{style:{maxWidth:720,margin:"0 auto",padding:"32px 24px 64px"},children:[(0,n.jsxs)("div",{style:{padding:"26px 26px 22px",borderRadius:16,marginBottom:22,position:"relative",overflow:"hidden",background:"radial-gradient(120% 140% at 0% 0%, rgba(0,255,136,0.10), rgba(52,152,219,0.06) 45%, rgba(255,255,255,0.02) 75%)",border:"1px solid rgba(0,255,136,0.22)"},children:[(0,n.jsx)("div",{style:{fontSize:11,fontWeight:800,letterSpacing:1.6,color:"#00ff88"},children:"YOUR SCANNER"}),(0,n.jsx)("h1",{style:{fontSize:27,fontWeight:800,margin:"6px 0 8px",lineHeight:1.2},children:em>0?"Your network is mapped. Send the scanner further.":"Map your network in four steps."}),(0,n.jsx)("div",{style:{fontSize:14.5,color:"#aab7c4",lineHeight:1.6,maxWidth:560},children:"It works in your own Chrome on this Mac, slowly and in the open, and keeps everything here. You launch it; it does the reading; you watch it go."}),(0,n.jsx)("div",{style:{display:"flex",gap:"8px 18px",flexWrap:"wrap",marginTop:16},children:["Never posts or messages anyone","Never sees your password","Nothing leaves this Mac","Stops the moment you say"].map(e=>(0,n.jsxs)("span",{style:{fontSize:12.5,color:"#cfe8dc",display:"flex",alignItems:"center",gap:6},children:[(0,n.jsx)("span",{style:{color:"#00ff88",fontWeight:900},children:"✓"}),e]},e))})]}),null===ev&&(0,n.jsx)("p",{style:{color:"#778",fontSize:13},children:"Checking your setup…"}),ev&&(0,n.jsx)(M,{onDone:function(e){et({sectors:e}),window.scrollTo(0,0)}}),en&&(0,n.jsx)(D,{sectors:en.sectors}),!1===ev&&(0,n.jsxs)(n.Fragment,{children:[eo?.person.id===ei&&(0,n.jsx)(A,{pick:eo,pages:v,setPages:k,li:ef,running:ec,s:o,canSearch:ey,busy:p,onUnpick:function(){es(null);try{window.history.replaceState(null,"",window.location.pathname)}catch{}},onStart:()=>el("bridge",{id:eo.person.id,maxPages:v})}),em>0&&!ec&&(0,n.jsx)(Z,{children:(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:14,flexWrap:"wrap"},children:[(0,n.jsxs)("div",{style:{flex:1,minWidth:200},children:[(0,n.jsxs)("b",{children:[em.toLocaleString()," connections"]}),eb.second>0&&(0,n.jsxs)(n.Fragment,{children:[", plus ",eb.second.toLocaleString()," people in their circles"]}),eb.third>0&&(0,n.jsxs)(n.Fragment,{children:[" and ",eb.third.toLocaleString()," from company scans"]}),"."," ",(0,n.jsx)("span",{style:{color:"#9aa"},children:"Your galaxy is ready."})]}),(0,n.jsx)(s.default,{href:"/",style:{padding:"9px 18px",borderRadius:7,fontSize:13.5,fontWeight:700,color:"#0a0a1a",textDecoration:"none",background:"linear-gradient(135deg, #FFD700, #FF6B35)"},children:"See your network →"})]})}),o?.photosWaiting>0&&!ec&&(0,n.jsx)(Z,{children:(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:14,flexWrap:"wrap"},children:[(0,n.jsxs)("div",{style:{flex:1,minWidth:200},children:[(0,n.jsxs)("b",{children:[o.photosWaiting.toLocaleString()," ",1===o.photosWaiting?"photo isn’t":"photos aren’t"," saved on this computer yet."]})," ",(0,n.jsx)("span",{style:{color:"#9aa"},children:"An older version kept them as links to LinkedIn. The app doesn’t load photos from LinkedIn while you browse, so those people show initials until the photos are saved here: now, or at the end of your next scan. An expired link can’t be saved; that person’s photo comes back when they’re next scanned."}),o&&!ed.dependencies&&(0,n.jsx)("div",{style:{color:"#8b9a9a",fontSize:12.5,marginTop:6},children:"Set up the scanner first (step 1 below): it saves them."})]}),(0,n.jsx)(V,{onClick:()=>el("photos"),disabled:p||!ed.dependencies,children:"Save photos"})]})}),ek&&(0,n.jsxs)(Z,{tone:"bad",children:[(0,n.jsx)("b",{children:"The last run stopped before it finished."}),eS&&(0,n.jsx)("div",{style:{marginTop:6,color:"#e8c4c4",whiteSpace:"pre-wrap",fontFamily:"ui-monospace, SFMono-Regular, Menlo, monospace",fontSize:12.5},children:eS}),(0,n.jsx)("div",{style:{color:"#9aa",marginTop:6},children:"The full log is below."})]}),eg&&(0,n.jsxs)(Z,{tone:"bad",children:[(0,n.jsx)("b",{children:"Can’t find the scanner files."}),(0,n.jsx)("br",{}),(0,n.jsxs)("span",{style:{color:"#9aa"},children:["Expected ",(0,n.jsx)("code",{style:_,children:"scripts/scrape.py"})," next to the app. If you downloaded a zip, run the app from inside the project folder."]})]}),ep&&(0,n.jsxs)(Z,{tone:"bad",children:[(0,n.jsx)("b",{children:"Google Chrome isn’t installed."}),(0,n.jsx)("br",{}),(0,n.jsxs)("span",{style:{color:"#9aa"},children:["The scanner drives your real Chrome. Install it from"," ",(0,n.jsx)("a",{href:"https://www.google.com/chrome/",target:"_blank",rel:"noreferrer",style:{color:"#3498DB"},children:"google.com/chrome"}),", then reload this page."]})]}),o&&eh.note&&(0,n.jsx)(Z,{tone:eh.done?void 0:"bad",children:eh.note}),(0,n.jsxs)("div",{role:"list","aria-label":"Scanning, step by step",style:{margin:"4px 0 8px"},children:[(0,n.jsx)("style",{children:U}),(0,n.jsx)(K,{n:1,label:"Get ready",state:ew(1,eh.done),children:(0,n.jsx)(q,{title:"Get your scanner ready",time:"A minute or two, once",done:eh.done,body:o?eh.text:"Checking…",launch:o&&eh.button&&(0,n.jsx)(X,{onClick:()=>el(eh.button.action),disabled:p||ec,children:eh.button.label})})}),(0,n.jsx)(K,{n:2,label:"Sign in",state:ew(2,!!ed.signedIn),children:(0,n.jsx)(q,{title:"Sign in to LinkedIn, once",time:"You do this part",done:!!ed.signedIn,body:ed.signedIn?"Signed in on this Mac. If LinkedIn ever asks for a security check, or a scan says you were signed out, open LinkedIn here and finish it by hand.":"A Chrome window opens. Sign in with your email and password there: the scanner never sees them. (“Continue with Google” can’t work in it, because Google blocks its sign-in in automated browsers.)",launch:ed.dependencies&&(0,n.jsx)(X,{onClick:()=>el("login"),disabled:p||ec||ej<2,quiet:!!ed.signedIn,children:ec&&"login"===o.action?"Waiting for you…":ed.signedIn?"Open LinkedIn again":"Open LinkedIn"})})}),(0,n.jsx)(K,{n:3,label:"Who you know",state:ew(3,em>0),children:(0,n.jsx)(q,{title:"Map the people you know",time:"About a minute and a half for 750 people",done:em>0,body:em>0?`${em.toLocaleString()} connections saved. Check for new picks up anyone you've added since; Scan it all again reads the whole list.`:"It reads your connections list and saves everyone here. After the first time, Check for new only looks at who you've added since.",launch:(0,n.jsx)(X,{onClick:()=>el("full"),disabled:!ey||ej<3,quiet:em>0,children:ec&&"full"===o.action?"Scanning…":em>0?"Scan it all again":"Scan my network"}),more:em>0&&(0,n.jsx)(V,{onClick:()=>el("refresh"),disabled:!ey||ej<3,primary:!0,children:ec&&"refresh"===o.action?"Checking…":"↻ Check for new"})})}),(0,n.jsx)(K,{n:4,label:"Who they know",state:ew(4,!1),last:!0,children:(0,n.jsxs)(q,{title:"Map who they know",time:"A small batch a day, slowly",body:(0,n.jsxs)(n.Fragment,{children:["It opens your connections one at a time and reads who ",(0,n.jsx)("i",{children:"they"})," know: that fills Degrees, Separation and Outlink. Every round picks up where the last one stopped."]}),children:[(0,n.jsx)(S,{cooldown:ef?.cooldown,disabled:p,onLift:()=>el("lift-cooldown")}),(0,n.jsxs)("div",{style:{display:"flex",gap:24,flexWrap:"wrap",alignItems:"center",marginTop:4},children:[(0,n.jsx)(u,{li:ef,running:!!ec,scanning:!!(ec&&o?.action?.startsWith("auto-bridge")),disabled:!ey||"score"===j&&!b.length,label:ec&&o?.action==="auto-bridge"?"Mapping…":"Map 2nd degree",sublabel:x?`${x} people`:"everyone",onScan:()=>el("auto-bridge",{maxBridges:x,tiers:"score"===j?b:[],order:j,maxPages:v,deeper:I,experimental:W}),onPace:p?void 0:e=>el("set-limits",{...ef?.limits||{},pace:e})}),(0,n.jsxs)("div",{style:{flex:"1 1 230px",display:"flex",flexDirection:"column",gap:12},children:[(0,n.jsxs)("label",{style:{display:"flex",gap:10,alignItems:"center",fontSize:13,color:"#cfd8d8"},children:["This round",(0,n.jsxs)("select",{value:x,onChange:e=>y(Number(e.target.value)),disabled:ec,style:G,children:[(0,n.jsx)("option",{value:5,children:"5 people"}),(0,n.jsx)("option",{value:10,children:"10 people"}),(0,n.jsx)("option",{value:25,children:"25 people"}),(0,n.jsx)("option",{value:0,children:"everyone — not advised"})]})]}),(0,n.jsxs)("label",{style:{display:"flex",gap:10,alignItems:"center",fontSize:13,color:"#cfd8d8"},children:["Start with",(0,n.jsxs)("select",{value:j,onChange:e=>w(e.target.value),disabled:ec,style:G,children:[(0,n.jsx)("option",{value:"newest",children:"Newest connections"}),(0,n.jsx)("option",{value:"score",children:"Highest tier"})]})]}),"score"===j&&(0,n.jsxs)("div",{style:{display:"flex",gap:8,alignItems:"center",flexWrap:"wrap"},children:[(0,n.jsx)("span",{style:{fontSize:12,color:"#8b9a9a"},children:"Work through"}),["S","A","B","C","D"].map(e=>{let t=b.includes(e);return(0,n.jsx)("button",{onClick:()=>m(n=>n.includes(e)?n.filter(n=>n!==e):[...n,e]),disabled:ec,style:{width:30,height:28,borderRadius:7,fontSize:12,fontWeight:700,cursor:ec?"not-allowed":"pointer",border:N,background:t?"rgba(52,152,219,0.22)":"rgba(255,255,255,0.05)",color:t?"#cfe6f7":"#667"},children:e},e)}),(0,n.jsx)("span",{style:{fontSize:11.5,color:"#667"},children:b.length?"":"pick at least one"})]}),"newest"===j&&(0,n.jsxs)("div",{style:{fontSize:12,color:"#8b9a9a",lineHeight:1.6},children:["Goes by the date you connected, newest first, across every tier. Run"," ",(0,n.jsx)("b",{children:"Check for new"})," first so your latest connections are in the list."]}),(0,n.jsx)(J,{})]})]})]})})]}),ec&&(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:12,marginTop:20,padding:"14px 16px",borderRadius:8,background:"rgba(0,255,136,0.06)",border:"1px solid rgba(0,255,136,0.3)"},children:[(0,n.jsx)(ee,{}),(0,n.jsxs)("div",{style:{flex:1,fontSize:13.5},children:[(0,n.jsx)("div",{style:{fontSize:11,fontWeight:800,letterSpacing:1.2,color:"#00ff88",marginBottom:2},children:"YOUR SCANNER IS WORKING"}),(0,n.jsx)("b",{children:E[o.action]||"Working"}),o.progress&&(0,n.jsx)(Q,{p:o.progress,action:o.action}),(0,n.jsx)("div",{style:{color:"#8b9a9a",fontSize:12.5,marginTop:2},children:"Stopping closes the browser cleanly and keeps everything found so far."})]}),(0,n.jsx)(V,{onClick:er,tone:"bad",children:"Stop"})]}),(0,n.jsxs)("details",{style:{margin:"18px 0 4px",borderRadius:10,border:N,background:"rgba(255,255,255,0.02)"},children:[(0,n.jsxs)("summary",{style:{padding:"12px 16px",cursor:"pointer",fontSize:13.5,fontWeight:650,color:"#cfd8d8"},children:["Fine-tune the scanner",(0,n.jsx)("span",{style:{fontWeight:500,color:"#778",marginLeft:8},children:"daily budget, how much of each list, the hidden window, paused lists"})]}),(0,n.jsxs)("div",{style:{padding:"4px 16px 16px",display:"flex",flexDirection:"column",gap:12},children:[(0,n.jsx)(T,{li:ef,disabled:p,onSetLimits:e=>el("set-limits",e)}),(0,n.jsxs)("label",{style:{display:"flex",gap:10,alignItems:"center",fontSize:12.5,color:"#cfd8d8"},children:["Read up to",(0,n.jsxs)("select",{value:v,onChange:e=>k(Number(e.target.value)),disabled:ec,style:G,children:[(0,n.jsx)("option",{value:100,children:"every page, to the end of their list"}),(0,n.jsx)("option",{value:50,children:"50 pages each"}),(0,n.jsx)("option",{value:25,children:"25 pages each"}),(0,n.jsx)("option",{value:10,children:"10 pages (~100 people) each"})]})]}),(0,n.jsxs)("label",{style:{display:"flex",gap:8,alignItems:"center",fontSize:12.5,color:"#cfd8d8",cursor:ec?"not-allowed":"pointer"},children:[(0,n.jsx)("input",{type:"checkbox",checked:I,onChange:e=>z(e.target.checked),disabled:ec}),"Also finish people already mapped, from the page each one stopped at"]}),(0,n.jsxs)("label",{style:{display:"flex",gap:8,alignItems:"flex-start",fontSize:12.5,color:"#b8c4c4",cursor:ec?"default":"pointer"},children:[(0,n.jsx)("input",{type:"checkbox",checked:W,disabled:ec,style:{marginTop:3},onChange:e=>{R(e.target.checked),window.dispatchEvent(new Event("six-degrees:experimental"))}}),(0,n.jsxs)("span",{children:[(0,n.jsx)("b",{style:{color:"#FFD700"},children:"Experimental:"})," all-day pacing. Up to 8 pages in a sitting, then an hour’s rest; searches only from 9:00 to 18:00; never more than 40 searches in a day or 200 in a week, however high your budget; two days’ rest after any check from LinkedIn; at the budget it waits instead of stopping; and it saves every page. It also reads LinkedIn’s own data beside the page text, to fill gaps and measure how the two compare. It keeps running while the app is open."]})]}),(0,n.jsxs)("label",{style:{display:"flex",gap:8,alignItems:"flex-start",fontSize:12.5,color:"#b8c4c4",cursor:ec?"default":"pointer"},children:[(0,n.jsx)("input",{type:"checkbox",checked:$,disabled:ec,style:{marginTop:3},onChange:e=>P(e.target.checked)}),(0,n.jsxs)("span",{children:[(0,n.jsx)("b",{style:{color:"#FFD700"},children:"Hide the Chrome window while scanning."})," Every scan runs with no window popping up; the status bar still shows what it’s doing, and Stop still works. Signing in always opens the window.",(0,n.jsx)("span",{style:{display:"block",marginTop:4,color:"#e0a080"},children:"The risks: a hidden Chrome is easier for LinkedIn to tell apart from a person, so it may make a warning or restriction more likely. If LinkedIn asks you to check it’s you (a code, a puzzle, signing in again) you won’t see it, and the scan will stop instead of waiting for you. Untick this and scan again to see what LinkedIn wants. It changes nothing about pacing or your daily budget."})]})]}),(0,n.jsxs)("div",{style:{fontSize:12,color:"#FFD700",lineHeight:1.6},children:["Every page is a LinkedIn search, so at ",(0,c.paceOf)(ef?.limits?.pace).label," it rests"," ",(0,c.paceOf)(ef?.limits?.pace).pagePause," seconds before each one and"," ",(0,c.paceOf)(ef?.limits?.pace).chunkCooldown/60==1?"a minute":`${(0,c.paceOf)(ef?.limits?.pace).chunkCooldown/60} minutes`," after every 10, and a long list can take"," ","about ",(0,d.circleScanCost)(v,ef?.limits?.pace).minutes," minutes a person. LinkedIn shows 100 pages of anyone’s connections at most. Free accounts have a monthly search limit: if LinkedIn says it has been reached, the scan saves what it read and stops, and carries on from that page next time. Keep batches small."]}),(0,n.jsxs)("div",{children:[(0,n.jsx)(V,{onClick:()=>el("auto-bridge-retry",{maxBridges:x,tiers:"score"===j?b:[],order:j,maxPages:v,deeper:I}),disabled:!ey||"score"===j&&!b.length,children:"Retry hidden ones"}),(0,n.jsx)("span",{style:{fontSize:12,color:"#8b9a9a",marginLeft:10},children:"People whose list was hidden last time, in case they’ve opened it."})]}),(0,n.jsx)(C,{paused:o?.paused||[],disabled:!ey,onResume:e=>el("resume",{id:e.id}),onResumeAll:()=>el("resume-all",{maxBridges:x})})]})]}),F&&(0,n.jsx)(Z,{tone:"bad",children:F}),o?.log?.length>0&&(0,n.jsxs)("details",{open:ek,style:{marginTop:20},children:[(0,n.jsxs)("summary",{style:{display:"flex",alignItems:"center",gap:10,cursor:"pointer",margin:"8px 0",fontSize:12,color:"#788",textTransform:"uppercase",letterSpacing:.6},children:[ec&&(0,n.jsx)(ee,{}),ec?"What it’s doing (the log)":"The last run’s log"]}),(0,n.jsx)("pre",{ref:B,style:{background:"rgba(0,0,0,0.45)",border:N,borderRadius:8,padding:14,maxHeight:280,overflow:"auto",margin:0,fontSize:12.5,lineHeight:1.7,color:"#b9c6c6",fontFamily:"ui-monospace, SFMono-Regular, Menlo, monospace",whiteSpace:"pre-wrap",wordBreak:"break-word"},children:o.log.join("\n")})]})]}),(0,n.jsxs)("div",{style:{marginTop:32,paddingTop:20,borderTop:N,fontSize:13.5,color:"#8b9a9a"},children:["Checking for updates has moved to"," ",(0,n.jsx)(s.default,{href:"/settings#updates",style:{color:"#3498DB"},children:"Settings →"})]}),(0,n.jsxs)("p",{style:{color:"#667",fontSize:12.5,lineHeight:1.7,marginTop:32},children:["Automating LinkedIn may go against its User Agreement, and accounts have been restricted for it. This runs locally against your own account, at your own risk. LinkedIn’s own CSV export is the supported route and needs none of this —"," ",(0,n.jsx)(s.default,{href:"/import",style:{color:"#3498DB"},children:"import a CSV instead"}),"."]})]}),(0,n.jsx)("style",{children:`@keyframes spin { to { transform: rotate(360deg) } }
15
+ @keyframes slide { 0% { transform: translateX(-100%) } 100% { transform: translateX(300%) } }`})]})}function A({pick:e,pages:t,setPages:i,li:o,running:a,s:r,canSearch:l,busy:c,onStart:h,onUnpick:p}){let{person:g,circle:u}=e,f=String(g.name||"").trim().split(/\s+/)[0]||"them",x=(0,d.circleScanCost)(t,o?.limits?.pace),y=a&&r?.target?.id===g.id,b=o?.leftToday!=null&&o.leftToday<x.searches;return(0,n.jsxs)("div",{style:{margin:"16px 0",padding:"16px 18px",borderRadius:10,fontSize:13.5,lineHeight:1.6,background:"rgba(0,255,136,0.05)",border:"1px solid rgba(0,255,136,0.3)"},children:[(0,n.jsx)("div",{style:{fontSize:11,fontWeight:700,color:"#00ff88",letterSpacing:.6,textTransform:"uppercase",marginBottom:4},children:"Scan one circle"}),(0,n.jsxs)("div",{style:{fontSize:16,fontWeight:700},children:[g.name," ",(0,n.jsxs)("span",{style:{fontSize:12,color:H[g.tier]||"#888"},children:[g.tier,"-tier"]})]}),g.unlocked_from_name&&(0,n.jsxs)("div",{style:{fontSize:12.5,color:"#9aa"},children:["Was in ",g.unlocked_from_name,"’s circle"]}),y?(0,n.jsxs)("div",{style:{marginTop:10},children:[(0,n.jsxs)("b",{children:["Scanning ",f,"’s circle."]})," It saves every 10 pages, and"," ",(0,n.jsx)(s.default,{href:`/?chain=${encodeURIComponent(g.id)}`,style:{color:"#00ff88"},children:"Bridge Chains shows it filling in →"})]}):(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{style:{color:"#b8c4c4",marginTop:10},children:["It costs ",(0,n.jsxs)("b",{children:[x.profileViews," profile view"]})," to find their list, then ",(0,n.jsx)("b",{children:"one LinkedIn search for every page"})," of it: up to ",x.searches," ",1===x.searches?"page":"pages",", about ",x.minutes," minutes.",o?.leftToday!=null&&(0,n.jsxs)(n.Fragment,{children:[" ",(0,n.jsx)("b",{children:o.leftToday})," of your ",o.limits?.daily," searches a day are left",b?"; the scan stops when they run out, and can carry on from that page later":"","."]})]}),"scanned"===u&&(0,n.jsx)("div",{style:{color:"#FFD700",fontSize:12.5,marginTop:6},children:"Their circle is already scanned. This reads their whole list again from page 1; to carry on from where a read stopped, use Resume on their card or in the Paused list below."}),"hidden"===u&&(0,n.jsx)("div",{style:{color:"#FFD700",fontSize:12.5,marginTop:6},children:"Their list was hidden last time it was tried. Trying again costs a profile view."}),(0,n.jsxs)("div",{style:{display:"flex",gap:10,alignItems:"center",flexWrap:"wrap",marginTop:12},children:[(0,n.jsxs)(V,{onClick:h,disabled:!l||c,primary:!0,children:["Scan ",f,"’s circle"]}),(0,n.jsxs)("select",{value:t,onChange:e=>i(Number(e.target.value)),disabled:a,style:G,"aria-label":"How many pages of their list to read",children:[(0,n.jsx)("option",{value:100,children:"every page, to the end of their list"}),(0,n.jsx)("option",{value:50,children:"up to 50 pages"}),(0,n.jsx)("option",{value:25,children:"up to 25 pages"}),(0,n.jsx)("option",{value:10,children:"up to 10 pages (~100 people)"})]}),(0,n.jsx)(V,{onClick:p,children:"Not now"})]}),!l&&(0,n.jsx)("div",{style:{fontSize:12.5,color:"#8b9a9a",marginTop:8},children:a?"Something else is running. One scan at a time: this one can start when it finishes.":o?.cooldown?"Scanning is paused for now (see below).":"The scanner isn’t ready yet: finish the steps below first."})]})]})}let H=y.TIER_COLORS;function Y(e,n){let[i,o]=(0,t.useState)(n);return(0,t.useEffect)(()=>{try{let n=localStorage.getItem(e);null!==n&&o(JSON.parse(n))}catch{}},[e]),[i,n=>{o(n);try{localStorage.setItem(e,JSON.stringify(n))}catch{}}]}let _={background:"rgba(255,255,255,0.08)",padding:"1px 5px",borderRadius:4},G={padding:"9px 10px",borderRadius:7,fontSize:13.5,fontWeight:600,background:"rgba(255,255,255,0.08)",color:"#fff",border:N},U=`
16
+ @keyframes stepPulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(0,255,136,.45); } 50% { box-shadow: 0 0 0 7px rgba(0,255,136,0); } }
17
+ .journey-now { animation: stepPulse 2.2s ease-in-out infinite; }
18
+ .launch-go { transition: transform .15s ease, box-shadow .2s ease, filter .2s ease; }
19
+ .launch-go:not(:disabled):hover { transform: translateY(-1px); box-shadow: 0 8px 30px rgba(0,255,136,.35); filter: brightness(1.08); }
20
+ .launch-go:not(:disabled):active { transform: translateY(0) scale(.98); }
21
+ @media (prefers-reduced-motion: reduce) { .journey-now { animation: none; } }
22
+ `;function K({n:e,label:t,state:i,last:o=!1,children:s}){let a="done"===i,r="now"===i;return(0,n.jsxs)("div",{role:"listitem","aria-label":`Step ${e}, ${t}: ${a?"done":r?"the one to do now":"still to come"}`,style:{display:"grid",gridTemplateColumns:"40px 1fr",columnGap:14},children:[(0,n.jsxs)("div",{style:{position:"relative",display:"flex",justifyContent:"center"},children:[(0,n.jsx)("span",{className:r?"journey-now":void 0,"aria-hidden":"true",style:{position:"relative",zIndex:1,width:38,height:38,borderRadius:"50%",display:"grid",placeItems:"center",fontSize:15,fontWeight:800,background:a?"rgba(0,255,136,0.16)":r?"linear-gradient(135deg, #00ff88, #1abc9c)":"#12142a",color:a?"#00ff88":r?"#04140c":"#778",border:`2px solid ${a?"rgba(0,255,136,0.55)":r?"transparent":"rgba(255,255,255,0.14)"}`},children:a?"✓":e}),!o&&(0,n.jsx)("span",{"aria-hidden":"true",style:{position:"absolute",top:40,bottom:0,left:"50%",width:2,marginLeft:-1,borderRadius:1,background:a?"linear-gradient(rgba(0,255,136,0.75), rgba(0,255,136,0.3))":"rgba(255,255,255,0.1)"}})]}),(0,n.jsxs)("div",{style:{minWidth:0,paddingBottom:16*!o,opacity:"next"===i?.75:1},children:[(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:10,minHeight:38,marginBottom:6},children:[(0,n.jsxs)("span",{style:{fontSize:12,fontWeight:800,letterSpacing:1.2,textTransform:"uppercase",color:a?"#00ff88":r?"#fff":"#889"},children:["Step ",e," · ",t]}),(0,n.jsx)("span",{style:{fontSize:10.5,fontWeight:800,padding:"2px 8px",borderRadius:10,color:a?"#00ff88":r?"#04140c":"#889",background:a?"rgba(0,255,136,0.12)":r?"#00ff88":"rgba(255,255,255,0.06)"},children:a?"Done":r?"Now":"Next"})]}),s]})]})}function q({title:e,time:t,body:i,launch:o,more:s,done:a=!1,children:r}){return(0,n.jsxs)("div",{style:{padding:a?"14px 18px":"18px 20px",borderRadius:14,background:a?"rgba(0,255,136,0.03)":"rgba(255,255,255,0.035)",border:`1px solid ${a?"rgba(0,255,136,0.18)":"rgba(255,255,255,0.1)"}`},children:[(0,n.jsxs)("div",{style:{display:"flex",alignItems:"baseline",gap:10,flexWrap:"wrap"},children:[(0,n.jsx)("span",{style:{fontSize:a?16:19,fontWeight:800},children:e}),t&&!a&&(0,n.jsxs)("span",{style:{fontSize:12,color:"#8fd9b6",marginLeft:"auto"},children:["⏱ ",t]})]}),(0,n.jsx)("div",{style:{fontSize:a?13:14,color:"#9fb0bb",lineHeight:1.6,margin:a?"6px 0 10px":"8px 0 14px",maxWidth:600},children:i}),r,(o||s)&&(0,n.jsxs)("div",{style:{display:"flex",gap:10,alignItems:"center",flexWrap:"wrap"},children:[o,s]})]})}function X({children:e,onClick:t,disabled:i,quiet:o=!1}){return(0,n.jsxs)("button",{type:"button",className:"launch-go",onClick:t,disabled:i,style:{display:"inline-flex",alignItems:"center",gap:10,padding:o?"10px 18px":"13px 26px",borderRadius:12,fontSize:o?14:16,fontWeight:800,cursor:i?"not-allowed":"pointer",color:i?"#667":o?"#cfe8dc":"#04140c",border:o?"1px solid rgba(0,255,136,0.35)":"none",background:i?"rgba(255,255,255,0.06)":o?"rgba(0,255,136,0.08)":"linear-gradient(135deg, #00ff88, #1abc9c)",boxShadow:i||o?"none":"0 4px 22px rgba(0,255,136,0.25)"},children:[(0,n.jsx)("span",{"aria-hidden":"true",style:{fontSize:o?11:13},children:"▶"}),e]})}function J(){return(0,n.jsxs)("details",{style:{fontSize:12.5,color:"#8b9a9a",lineHeight:1.6},children:[(0,n.jsx)("summary",{style:{cursor:"pointer",color:"#aab7c4",fontWeight:600},children:"Why so slow?"}),(0,n.jsxs)("div",{style:{marginTop:6},children:["This is the part LinkedIn notices. While this app was being built, a real account was restricted for a while after roughly ",(0,n.jsx)("b",{children:"19 people in one sitting"}),". So: a small batch, a day’s rest, another batch, and stop the moment LinkedIn mentions unusual activity. Most people hide their connections; those are noted and never tried again. A list is read to the end, saving every 10 pages, and a stopped read carries on from the same page."]})]})}function V({children:e,onClick:t,disabled:i,primary:o,tone:s}){return(0,n.jsx)("button",{onClick:t,disabled:i,style:{padding:"9px 18px",borderRadius:7,fontSize:13.5,fontWeight:650,color:i?"#667":"#fff",background:i?"rgba(255,255,255,0.05)":"bad"===s?"rgba(255,80,80,0.15)":o?"linear-gradient(135deg, #9B59B6, #3498DB)":"rgba(255,255,255,0.08)",border:N,cursor:i?"not-allowed":"pointer"},children:e})}function Z({children:e,tone:t}){return(0,n.jsx)("div",{style:{margin:"16px 0",padding:"14px 16px",borderRadius:8,fontSize:13.5,lineHeight:1.6,background:"bad"===t?"rgba(255,80,80,0.08)":"rgba(255,255,255,0.05)",border:`1px solid ${"bad"===t?"rgba(255,80,80,0.3)":"rgba(255,255,255,0.12)"}`},children:e})}function Q({p:e,action:t}){let i;if("saving"===e.kind)return(0,n.jsxs)("div",{style:{margin:"8px 0 6px"},children:[(0,n.jsx)("div",{role:"progressbar","aria-label":"Saving",style:{height:8,borderRadius:4,overflow:"hidden",background:"rgba(255,255,255,0.08)",marginBottom:6},children:(0,n.jsx)("div",{style:{width:"35%",height:"100%",borderRadius:4,background:"linear-gradient(90deg, #9B59B6, #3498DB)",animation:"slide 1.2s ease-in-out infinite"}})}),(0,n.jsx)("div",{style:{fontSize:12.5,color:"#cfe6f7"},children:"Saving to your network and fetching photos — this can take a minute or two."})]});let o=Math.max(0,Math.min(100,Math.round(e.done/e.total*100)));return i="download"===e.kind?`Downloading Python: ${e.done.toFixed(1)} of ${e.total.toFixed(1)} MB \xb7 ${o}%`:"batch"===e.kind?`Person ${e.current} of ${e.total}`:"refresh"===t?`Looked at ${e.done.toLocaleString()} so far — stops once it reaches people already saved`:`${e.done.toLocaleString()} of ${e.total.toLocaleString()} connections \xb7 ${o}%`,(0,n.jsxs)("div",{style:{margin:"8px 0 6px"},children:["refresh"!==t&&(0,n.jsx)("div",{role:"progressbar","aria-valuemin":0,"aria-valuemax":e.total,"aria-valuenow":e.done,"aria-label":i,style:{height:8,borderRadius:4,overflow:"hidden",background:"rgba(255,255,255,0.08)",marginBottom:6},children:(0,n.jsx)("div",{style:{width:`${o}%`,height:"100%",borderRadius:4,background:"linear-gradient(90deg, #9B59B6, #3498DB)",transition:"width 0.6s ease"}})}),(0,n.jsx)("div",{style:{fontSize:12.5,color:"#cfe6f7"},children:i})]})}function ee(){return(0,n.jsx)("span",{style:{width:11,height:11,borderRadius:"50%",display:"inline-block",border:"2px solid rgba(255,255,255,0.2)",borderTopColor:"#3498DB",animation:"spin 0.8s linear infinite"}})}e.s(["default",0,function(){return(0,n.jsx)(o.default,{children:(0,n.jsx)(t.Suspense,{children:(0,n.jsx)(O,{})})})}],68863)}]);