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