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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +14 -14
- 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 +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 +20 -19
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +20 -19
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- 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.nft.json +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.nft.json +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.nft.json +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.nft.json +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.nft.json +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.nft.json +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.nft.json +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.nft.json +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.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.nft.json +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 +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.nft.json +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 +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 +24 -23
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +22 -21
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +22 -21
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch/page.js +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 +24 -23
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/page.js +1 -1
- 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 +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 +24 -23
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/profile/page.js +1 -1
- 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 +24 -23
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/queue/page.js +1 -1
- 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 +24 -23
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/scores/page.js +1 -1
- 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 +24 -23
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/settings/page.js +1 -1
- 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 +24 -23
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/setup/page.js +1 -1
- 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 +24 -23
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/social/page.js +1 -1
- 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 +24 -23
- package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__05_3-22._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0um1uey._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__13dx4sd._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__15wc9e-._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__18bcnu4._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tir0vc._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20rwfdw._.js +2 -2
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -7
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +2 -2
- package/.next/standalone/.next/server/chunks/lib_0851sk0._.js +5 -5
- package/.next/standalone/.next/server/chunks/lib_1q7p2ro._.js +5 -5
- package/.next/standalone/.next/server/chunks/lib_1r6l86l._.js +5 -5
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +9 -9
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08oxiqh._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_03l7vge._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_08g31gm._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0gk_ofx._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0nwsf93._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0vho1lb._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0jdkqgi._.js → _1-658fq._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1bc46kz._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1f_n00p._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1fm1ih_._.js +9 -0
- package/.next/standalone/.next/server/chunks/ssr/_1k2nmbw._.js +9 -0
- package/.next/standalone/.next/server/chunks/ssr/_2105jz8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_141w189._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_components_0t908t-._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_components_0uuff77._.js +13 -0
- package/.next/standalone/.next/server/chunks/ssr/app_components_1j3q-h5._.js +13 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0gtv9jb._.js → app_components_CompanyScores_1g0mytz.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_components_settings_SectorPicker_1c-29qw.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_import_page_1fmu6_l.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +14 -12
- 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 -6
- 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 +4 -4
- package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{lib_0i8v-s3._.js → lib_1c4ieuw._.js} +2 -2
- 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/02pq76u7y4v9-.js +22 -0
- package/.next/standalone/.next/static/chunks/0cmq0exd4d0h9.js +1 -0
- package/.next/{static/chunks/2wgz9ietkqqe6.js → standalone/.next/static/chunks/0i02yxppzds1o.js} +1 -1
- package/.next/standalone/.next/static/chunks/0m_p4k-q2qh--.js +1 -0
- package/.next/standalone/.next/static/chunks/0oyslj62jkubd.js +1 -0
- package/.next/standalone/.next/static/chunks/{1x_ysly42-teq.css → 0vo-l8hxneba6.css} +1 -1
- package/.next/{static/chunks/3sowbvbaj1s6n.js → standalone/.next/static/chunks/0ws7zy0w57ufi.js} +1 -1
- package/.next/standalone/.next/static/chunks/0yh8z2tbrc5i_.js +1 -0
- package/.next/standalone/.next/static/chunks/172e10mi-tpo_.js +34 -0
- package/.next/standalone/.next/static/chunks/1ei5vbe2uw6fl.js +1 -0
- package/.next/standalone/.next/static/chunks/1f7_pl_wq7lh3.js +11 -0
- package/.next/standalone/.next/static/chunks/1jzbe7zdx9vbj.js +2 -0
- package/.next/standalone/.next/static/chunks/1m3017gp790pw.js +45 -0
- package/.next/standalone/.next/static/chunks/1t20e8u0st7ep.js +1 -0
- package/.next/standalone/.next/static/chunks/2-ch9rm27eril.js +11 -0
- package/.next/standalone/.next/static/chunks/2ldv-v3s9yaif.js +1 -0
- package/.next/standalone/.next/static/chunks/3bp284gjf7dyq.js +1 -0
- package/.next/standalone/.next/static/chunks/3k1f6clbn3gj3.js +7 -0
- package/.next/standalone/.next/static/chunks/3l4t8je6iguzc.js +1 -0
- package/.next/standalone/.next/static/chunks/{0muwt9bykzr-1.js → 3tplzuc9jk-tl.js} +1 -1
- package/.next/standalone/.next/static/chunks/42m0bqr8pbovz.js +11 -0
- package/.next/standalone/CHANGELOG.md +61 -0
- package/.next/standalone/README.md +22 -8
- package/.next/standalone/app/api/scraper/route.js +40 -3
- package/.next/standalone/app/api/update/route.js +12 -6
- package/.next/standalone/app/components/AppHeader.js +257 -0
- package/.next/standalone/app/components/Avatar.js +2 -1
- package/.next/standalone/app/components/ChainView.js +12 -12
- package/.next/standalone/app/components/FilterPanel.js +20 -12
- package/.next/standalone/app/components/ForceGraph.js +163 -25
- package/.next/standalone/app/components/GalaxyLab.js +43 -12
- package/.next/standalone/app/components/GridView.js +4 -4
- package/.next/standalone/app/components/LinkedInLimits.js +2 -1
- package/.next/standalone/app/components/ListView.js +3 -3
- package/.next/standalone/app/components/NoteDetail.js +2 -1
- package/.next/standalone/app/components/OnboardingGate.js +2 -2
- package/.next/standalone/app/components/OutlinkQuest.js +2 -1
- package/.next/standalone/app/components/PathsAnalyzer.js +3 -2
- package/.next/standalone/app/components/PeopleMap.js +2 -1
- package/.next/standalone/app/components/RingsView.js +3 -3
- package/.next/standalone/app/components/ScanTrail.js +85 -0
- package/.next/standalone/app/components/SeparationView.js +100 -77
- package/.next/standalone/app/components/Sidebar.js +4 -4
- package/.next/standalone/app/components/TheirCircle.js +2 -1
- package/.next/standalone/app/components/ThemeLoader.js +9 -0
- package/.next/standalone/app/components/UpdatePanel.js +20 -2
- package/.next/standalone/app/components/settings/AppearanceSection.js +226 -0
- package/.next/standalone/app/components/settings/NetworkHealthSection.js +2 -1
- package/.next/standalone/app/components/ui.js +2 -1
- package/.next/standalone/app/globals.css +70 -2
- package/.next/standalone/app/import/page.js +3 -3
- package/.next/standalone/app/layout.js +11 -2
- package/.next/standalone/app/page.js +69 -215
- package/.next/standalone/app/paths/page.js +8 -21
- package/.next/standalone/app/profile/page.js +11 -10
- package/.next/standalone/app/queue/page.js +8 -21
- package/.next/standalone/app/scores/page.js +1 -1
- package/.next/standalone/app/settings/page.js +7 -3
- package/.next/standalone/app/setup/page.js +83 -88
- package/.next/standalone/app/social/Crm.js +2 -1
- package/.next/standalone/app/social/SocialHub.js +2 -2
- package/.next/standalone/app/social/page.js +1 -1
- package/.next/standalone/lib/data-import.js +1 -0
- package/.next/standalone/lib/galaxy-export.js +2 -1
- package/.next/standalone/lib/galaxy-lab.js +24 -8
- package/.next/standalone/lib/level.js +51 -0
- package/.next/standalone/lib/release.js +13 -7
- package/.next/standalone/lib/scanner-python.js +38 -8
- package/.next/standalone/lib/scraper-client.js +2 -1
- package/.next/standalone/lib/separation.js +4 -2
- package/.next/standalone/lib/theme-store.js +61 -0
- package/.next/standalone/lib/themes.js +186 -0
- package/.next/standalone/lib/tiers.js +2 -8
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +4 -4
- package/.next/standalone/scripts/build-desktop.mjs +325 -0
- package/.next/standalone/scripts/build-site.mjs +85 -15
- package/.next/standalone/scripts/gen-test-network.mjs +93 -0
- package/.next/standalone/scripts/next.mjs +20 -0
- package/.next/standalone/scripts/readme_buttons.py +3 -0
- package/.next/standalone/scripts/requirements.txt +18 -1
- package/.next/standalone/scripts/site-news.mjs +54 -9
- package/.next/standalone/scripts/smoke-desktop.mjs +177 -0
- package/.next/standalone/scripts/windows/six-degrees.iss +100 -0
- package/.next/static/chunks/02pq76u7y4v9-.js +22 -0
- package/.next/static/chunks/0cmq0exd4d0h9.js +1 -0
- package/.next/{standalone/.next/static/chunks/2wgz9ietkqqe6.js → static/chunks/0i02yxppzds1o.js} +1 -1
- package/.next/static/chunks/0m_p4k-q2qh--.js +1 -0
- package/.next/static/chunks/0oyslj62jkubd.js +1 -0
- package/.next/static/chunks/{1x_ysly42-teq.css → 0vo-l8hxneba6.css} +1 -1
- package/.next/{standalone/.next/static/chunks/3sowbvbaj1s6n.js → static/chunks/0ws7zy0w57ufi.js} +1 -1
- package/.next/static/chunks/0yh8z2tbrc5i_.js +1 -0
- package/.next/static/chunks/172e10mi-tpo_.js +34 -0
- package/.next/static/chunks/1ei5vbe2uw6fl.js +1 -0
- package/.next/static/chunks/1f7_pl_wq7lh3.js +11 -0
- package/.next/static/chunks/1jzbe7zdx9vbj.js +2 -0
- package/.next/static/chunks/1m3017gp790pw.js +45 -0
- package/.next/static/chunks/1t20e8u0st7ep.js +1 -0
- package/.next/static/chunks/2-ch9rm27eril.js +11 -0
- package/.next/static/chunks/2ldv-v3s9yaif.js +1 -0
- package/.next/static/chunks/3bp284gjf7dyq.js +1 -0
- package/.next/static/chunks/3k1f6clbn3gj3.js +7 -0
- package/.next/static/chunks/3l4t8je6iguzc.js +1 -0
- package/.next/static/chunks/{0muwt9bykzr-1.js → 3tplzuc9jk-tl.js} +1 -1
- package/.next/static/chunks/42m0bqr8pbovz.js +11 -0
- package/CHANGELOG.md +61 -0
- package/README.md +22 -8
- package/lib/data-import.js +1 -0
- package/lib/galaxy-export.js +2 -1
- package/lib/galaxy-lab.js +24 -8
- package/lib/level.js +51 -0
- package/lib/release.js +13 -7
- package/lib/scanner-python.js +38 -8
- package/lib/scraper-client.js +2 -1
- package/lib/separation.js +4 -2
- package/lib/theme-store.js +61 -0
- package/lib/themes.js +186 -0
- package/lib/tiers.js +2 -8
- package/package.json +4 -4
- package/scripts/requirements.txt +18 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljcioj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16hs8kw._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_01kacen._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0xi-fbs._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0xmwmfv._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1-b9hj7._.js +0 -9
- package/.next/standalone/.next/server/chunks/ssr/_112ewem._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_16ab63q._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1fxm0-s._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1ra1d3r._.js +0 -9
- package/.next/standalone/.next/server/chunks/ssr/app_components_ui_0pdmz2z.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/lib_13rapwj._.js +0 -3
- package/.next/standalone/.next/static/chunks/03bziijw5srol.js +0 -1
- package/.next/standalone/.next/static/chunks/0bgq8wmopl_ig.js +0 -7
- package/.next/standalone/.next/static/chunks/0ezus6p2eb-vg.js +0 -1
- package/.next/standalone/.next/static/chunks/0g_fy2034bp0x.js +0 -34
- package/.next/standalone/.next/static/chunks/0q5dru4q6gxl5.js +0 -22
- package/.next/standalone/.next/static/chunks/0w-byiwb2zax8.js +0 -2
- package/.next/standalone/.next/static/chunks/1g7jcovdejc9-.js +0 -1
- package/.next/standalone/.next/static/chunks/1i51yylxdzht1.js +0 -1
- package/.next/standalone/.next/static/chunks/1zf8swaasjf1e.js +0 -43
- package/.next/standalone/.next/static/chunks/2dwtoszeq91us.js +0 -1
- package/.next/standalone/.next/static/chunks/2rqtpccruavf2.js +0 -1
- package/.next/standalone/.next/static/chunks/30x3oq_st7a5r.js +0 -1
- package/.next/standalone/.next/static/chunks/355ryio1b7sm1.js +0 -1
- package/.next/standalone/.next/static/chunks/3ap5hgma7n0o7.js +0 -1
- package/.next/standalone/.next/static/chunks/3fci6qi39-1n5.js +0 -1
- package/.next/static/chunks/03bziijw5srol.js +0 -1
- package/.next/static/chunks/0bgq8wmopl_ig.js +0 -7
- package/.next/static/chunks/0ezus6p2eb-vg.js +0 -1
- package/.next/static/chunks/0g_fy2034bp0x.js +0 -34
- package/.next/static/chunks/0q5dru4q6gxl5.js +0 -22
- package/.next/static/chunks/0w-byiwb2zax8.js +0 -2
- package/.next/static/chunks/1g7jcovdejc9-.js +0 -1
- package/.next/static/chunks/1i51yylxdzht1.js +0 -1
- package/.next/static/chunks/1zf8swaasjf1e.js +0 -43
- package/.next/static/chunks/2dwtoszeq91us.js +0 -1
- package/.next/static/chunks/2rqtpccruavf2.js +0 -1
- package/.next/static/chunks/30x3oq_st7a5r.js +0 -1
- package/.next/static/chunks/355ryio1b7sm1.js +0 -1
- package/.next/static/chunks/3ap5hgma7n0o7.js +0 -1
- package/.next/static/chunks/3fci6qi39-1n5.js +0 -1
- /package/.next/standalone/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_ssgManifest.js +0 -0
- /package/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_buildManifest.js +0 -0
- /package/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,33821,e=>{"use strict";var t=e.i(43476),n=e.i(71645),r=e.i(22016),o=e.i(18566),i=e.i(26005),l=e.i(76760),a=e.i(30215);let s=["#FFD700","#9B59B6","#3498DB"],c=`
|
|
2
|
+
@keyframes trail-in { from { transform: scale(0); opacity: 0; } }
|
|
3
|
+
@keyframes trail-pulse { 0%, 100% { opacity: 0.45; transform: scale(0.8); } 50% { opacity: 1; transform: scale(1.25); } }
|
|
4
|
+
.scan-trail { transition: opacity 1.2s ease; }
|
|
5
|
+
.scan-trail .dot { animation: trail-in 0.35s cubic-bezier(0.2, 0.9, 0.3, 1.3); }
|
|
6
|
+
.scan-trail .dot.newest { box-shadow: 0 0 6px 1px currentColor; }
|
|
7
|
+
.scan-trail .seed { animation: trail-pulse 1.4s ease-in-out infinite; }
|
|
8
|
+
@media (prefers-reduced-motion: reduce) {
|
|
9
|
+
.scan-trail .dot, .scan-trail .seed { animation: none; }
|
|
10
|
+
.scan-trail { transition: none; }
|
|
11
|
+
}`;function u({inset:e}){let o=(0,a.default)(),i=(0,n.useRef)(null),[l,d]=(0,n.useState)(60),[f,p]=(0,n.useState)({on:!1,pages:0,label:""}),g=o.running&&!o.pending,m=o.target?.name?`Scanning ${o.target.name}`:"Scanning";g&&(!f.on||f.pages!==(o.pages||0)||f.label!==m)&&p({on:!0,pages:o.pages||0,label:m}),(0,n.useEffect)(()=>{if(g||!f.on)return;let e=setTimeout(()=>p(e=>({...e,on:!1})),4e3);return()=>clearTimeout(e)},[g,f.on]),(0,n.useEffect)(()=>{let e=i.current;if(!e)return;let t=new ResizeObserver(()=>d(Math.max(8,Math.floor(e.clientWidth/9)-4)));return t.observe(e),()=>t.disconnect()},[]);let{pages:h}=f,b=Math.max(0,h-l),y=[];for(let e=b;e<h;e++)y.push(e);let x=`${f.label}: ${0===h?"starting":`${h.toLocaleString("en-US")} page${1===h?"":"s"} read`}. Open the Scan page`;return(0,t.jsxs)("div",{ref:i,"aria-hidden":!f.on,style:{position:"absolute",left:e,right:e,bottom:-4,height:7,pointerEvents:"none"},children:[(0,t.jsx)("style",{children:c}),(0,t.jsxs)(r.default,{href:"/setup",title:x,tabIndex:f.on?0:-1,className:"scan-trail",style:{position:"absolute",left:0,top:0,height:7,display:"flex",alignItems:"center",gap:4,pointerEvents:f.on?"auto":"none",opacity:+!!f.on,textDecoration:"none"},children:[b>0&&(0,t.jsxs)("span",{style:{fontSize:9,fontWeight:700,color:"#889",marginRight:2,fontVariantNumeric:"tabular-nums"},children:["+",b.toLocaleString("en-US")]}),y.map(e=>{let n=s[e%s.length];return(0,t.jsx)("span",{className:`dot${e===h-1?" newest":""}`,style:{width:5,height:5,borderRadius:"50%",background:n,color:n,flexShrink:0}},e)}),f.on&&(0,t.jsx)("span",{className:"seed",style:{width:5,height:5,borderRadius:"50%",background:"#00ff88",flexShrink:0}})]})]})}var d=e.i(15812),f=e.i(40996),p=e.i(19821),g=e.i(98658),m=e.i(64745);let h={fontWeight:700,margin:0,whiteSpace:"nowrap",background:"linear-gradient(135deg, #FFD700, #9B59B6, #3498DB)",WebkitBackgroundClip:"text",WebkitTextFillColor:"transparent"};function b({isMobile:e,onOpen:r,openId:i}){let{userId:l}=(0,d.useUser)(),a=(0,o.useRouter)(),[s,c]=(0,n.useState)([]),[u,f]=(0,n.useState)(!1),p=(0,n.useRef)(!1),g=(0,n.useRef)(r);(0,n.useEffect)(()=>{g.current=r}),(0,n.useEffect)(()=>{l&&fetch(`/api/notifications?userId=${l}`).then(e=>e.json()).then(e=>{let t=e.notifications||[];c(t);let n=null!=i&&!p.current&&t.find(e=>String(e.id)===String(i));n&&g.current&&(p.current=!0,g.current(n))}).catch(()=>{})},[l,i]);let m=s.filter(e=>!e.seen).length,h=e=>{e.seen||(fetch("/api/notifications",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:"mark-seen",id:e.id})}).catch(()=>{}),c(t=>t.map(t=>t.id===e.id?{...t,seen:!0}:t))),f(!1),r?r(e):a.push(`/?note=${encodeURIComponent(e.id)}`)};return(0,t.jsxs)("div",{style:{position:"relative"},children:[(0,t.jsxs)("button",{onClick:()=>f(!u),title:"Notifications","aria-label":m?`Notifications, ${m} new`:"Notifications",style:{width:e?28:32,height:e?28:32,borderRadius:"50%",border:"none",cursor:"pointer",background:"rgba(255,255,255,0.06)",color:"#888",fontSize:e?12:14,display:"flex",alignItems:"center",justifyContent:"center",position:"relative"},children:["🔔",m>0&&(0,t.jsx)("span",{style:{position:"absolute",top:-2,right:-2,width:16,height:16,borderRadius:"50%",background:"#ff5050",color:"#fff",fontSize:9,fontWeight:700,display:"flex",alignItems:"center",justifyContent:"center"},children:m})]}),u&&(0,t.jsxs)("div",{style:{position:"absolute",top:40,right:0,width:320,maxHeight:400,background:"color-mix(in srgb, var(--sd-bg) 97%, transparent)",border:"1px solid rgba(255,255,255,0.1)",borderRadius:12,overflow:"hidden",zIndex:100,backdropFilter:"blur(12px)",WebkitBackdropFilter:"blur(12px)",boxShadow:"0 8px 32px rgba(0,0,0,0.5)"},children:[(0,t.jsxs)("div",{style:{padding:"12px 16px",borderBottom:"1px solid rgba(255,255,255,0.08)",display:"flex",justifyContent:"space-between",alignItems:"center"},children:[(0,t.jsx)("span",{style:{fontWeight:700,fontSize:14},children:"Notifications"}),m>0&&(0,t.jsx)("button",{onClick:()=>{fetch("/api/notifications",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:"mark-all-seen"})}),c(e=>e.map(e=>({...e,seen:!0})))},style:{background:"none",border:"none",color:"#3498DB",fontSize:11,cursor:"pointer",fontWeight:600},children:"Mark all read"})]}),(0,t.jsx)("div",{style:{overflowY:"auto",maxHeight:340},children:0===s.length?(0,t.jsx)("div",{style:{padding:20,textAlign:"center",color:"#555",fontSize:12},children:"No notifications yet"}):s.slice(0,20).map(e=>(0,t.jsx)("div",{role:"button",tabIndex:0,onClick:()=>h(e),onKeyDown:t=>{"Enter"===t.key&&h(e)},onMouseEnter:e=>{e.currentTarget.style.background="rgba(255,255,255,0.06)"},onMouseLeave:t=>{t.currentTarget.style.background=e.seen?"transparent":"rgba(255,215,0,0.04)"},style:{padding:"10px 16px",borderBottom:"1px solid rgba(255,255,255,0.04)",cursor:"pointer",background:e.seen?"transparent":"rgba(255,215,0,0.04)"},children:(0,t.jsxs)("div",{style:{display:"flex",gap:8,alignItems:"flex-start"},children:[(0,t.jsx)("span",{style:{fontSize:16},children:e.icon||"📌"}),(0,t.jsxs)("div",{style:{flex:1},children:[(0,t.jsxs)("div",{style:{fontSize:12,fontWeight:600,color:e.seen?"#888":"#fff"},children:[e.title,(0,t.jsx)("span",{style:{color:"#556",marginLeft:6},children:"›"})]}),e.message&&(0,t.jsx)("div",{style:{fontSize:10,color:"#666",marginTop:2},children:e.message}),(0,t.jsx)("div",{style:{fontSize:9,color:"#444",marginTop:3},children:new Date(e.created_at).toLocaleDateString()})]})]})},e.id))})]})]})}function y({isMobile:e}){let n=(0,a.default)(),r=n.running&&"refresh"===n.action,o=(0,f.busyReason)(n);return(0,t.jsxs)("button",{onClick:async()=>{try{let e=(0,f.notReadyMessage)(await (0,f.scraperStatus)());if(e)return void alert(e);if(!window.confirm("Check for new connections?\n\nThis opens a Chrome window on your LinkedIn connections list and reads it from the newest, stopping once it reaches people already saved. It usually takes under a minute. It reads your own list, not a search, so it doesn’t use your search budget, but like any scan it is LinkedIn traffic from your account."))return;await (0,f.beginScrape)("refresh"),alert("Checking for new connections — watch it on the Scan page.")}catch(e){alert(e.message)}},disabled:!!o,title:o?`${o}. One scan at a time.`:"Check for new connections: reads your LinkedIn connections list from the newest","aria-label":"Check for new connections",style:{height:e?28:32,padding:e?"0 10px":"0 14px",borderRadius:16,border:"1px solid rgba(0,255,136,0.3)",cursor:o?"not-allowed":"pointer",opacity:o?.45:1,background:"rgba(0,255,136,0.08)",color:"#bff5d9",fontSize:e?11:12.5,fontWeight:700,display:"flex",alignItems:"center",gap:6,whiteSpace:"nowrap",flexShrink:0},children:[(0,t.jsx)("span",{"aria-hidden":"true",style:{fontSize:e?12:14},children:"↻"}),r?"Checking…":e?"New":"Check for new"]})}let x=null;function w({isMobile:e,csvMode:r,level:o}){let{userId:i}=(0,d.useUser)(),l=m.IS_DEMO?"sample":r?"csv":"own",a=(0,n.useSyncExternalStore)(g.watchLevel,()=>(0,g.levelNow)(l),()=>null);(0,n.useEffect)(()=>{null!=o?(0,g.rememberLevel)(o,l):null!=a||m.IS_DEMO||r||!i||(x||(x=(0,p.loadNetwork)(i).then(e=>(0,g.networkLevel)(e.degree1,e.degree2)).catch(()=>(x=null,null))),x.then(e=>{null!=e&&(0,g.rememberLevel)(e,l)}))},[o,a,r,l,i]);let s=o??a;return(0,t.jsx)("a",{href:m.IS_DEMO?"/launch":r?"/import":"/profile",title:null!=s?`Your profile \xb7 level ${s}`:"Your profile",style:{width:e?30:36,height:e?30:36,borderRadius:"50%",display:"flex",alignItems:"center",justifyContent:"center",background:"linear-gradient(135deg, #FFD700, #FF6B35)",color:"#000",fontWeight:800,fontSize:e?11:14,textDecoration:"none",marginLeft:e?2:4,flexShrink:0},children:s??""})}e.s(["default",0,function({active:e,isMobile:o,csvMode:a=!1,onMode:s,chips:c,level:d,onOpenNote:f,openNoteId:p=null,brand:g="h1",children:x}){let S=function(){let[e,t]=(0,n.useState)(!1);return(0,n.useEffect)(()=>{let e=()=>t(window.innerWidth<768);return e(),window.addEventListener("resize",e),()=>window.removeEventListener("resize",e)},[]),e}(),k=o??S,v=!m.IS_DEMO&&!a;return(0,t.jsxs)("header",{style:{position:"relative",padding:k?"10px 12px":"20px 30px",borderBottom:"1px solid rgba(255,255,255,0.1)",flexShrink:0,background:"var(--sd-header)",backdropFilter:"var(--sd-header-blur)",WebkitBackdropFilter:"var(--sd-header-blur)"},children:[(0,t.jsxs)("div",{style:k?{display:"flex",alignItems:"center",justifyContent:"space-between",flexWrap:"wrap",gap:6,marginBottom:4}:{display:"grid",gridTemplateColumns:"1fr auto 1fr",alignItems:"center",gap:16,marginBottom:8},children:[(0,t.jsx)(r.default,{href:"/",style:{textDecoration:"none",justifySelf:"start"},children:(0,t.jsx)(g,{style:{...h,fontSize:k?16:28},children:"Six Degrees"})}),(0,t.jsx)(i.default,{active:e,isMobile:k,csvMode:a,onMode:s,after:v?(0,t.jsx)(l.default,{isMobile:k}):null,children:c}),(0,t.jsxs)("div",{style:{display:"flex",alignItems:"center",justifyContent:"flex-end",justifySelf:"end",gap:k?4:8},children:[!m.IS_DEMO&&(0,t.jsx)(r.default,{href:"/settings",title:"Settings","aria-label":"Settings",style:{width:k?28:32,height:k?28:32,borderRadius:"50%",flexShrink:0,background:"rgba(255,255,255,0.06)",color:"#888",fontSize:k?14:16,textDecoration:"none",display:"flex",alignItems:"center",justifyContent:"center"},children:"⚙︎"}),v&&(0,t.jsx)(b,{isMobile:k,onOpen:f,openId:p}),v&&(0,t.jsx)(y,{isMobile:k}),(0,t.jsx)(w,{isMobile:k,csvMode:a,level:d})]})]}),x,v&&(0,t.jsx)(u,{inset:k?12:30})]})}],33821)},26005,76760,e=>{"use strict";var t=e.i(43476),n=e.i(22016),r=e.i(64745);let o=[{key:"network",label:"Network Circle",short:"Circle",on:{background:"#fff",color:"#000"},off:{background:"transparent",color:"#888"}},{key:"degrees",label:"Degrees",on:{background:"linear-gradient(135deg, #FFD700, #FF6B35)",color:"#000"},off:{background:"rgba(255,255,255,0.12)",color:"#fff"}},{key:"separation",label:"Separation",on:{background:"linear-gradient(135deg, #00E5FF, #FFD700 55%, #FF7043)",color:"#000"},off:{background:"rgba(255,255,255,0.12)",color:"#fff"}}],i=[{key:"paths",href:"/paths",label:"Paths",color:"#00ff88",on:"linear-gradient(135deg, #00ff88, #3498DB)",demo:!0},{key:"outlink",href:"/queue",label:"Outlink",color:"#FF6B35",on:"linear-gradient(135deg, #FF6B35, #FFD700)",own:!0},{key:"scan",href:"/setup",label:"Scan",color:"#666",on:"linear-gradient(135deg, #00ff88, #1abc9c)",own:!0}];e.s(["default",0,function({active:e,isMobile:l=!1,csvMode:a=!1,onMode:s,children:c,after:u}){let d={padding:l?"6px 10px":"8px 16px",borderRadius:6,border:"none",fontSize:l?11:13,fontWeight:600,textDecoration:"none"};return(0,t.jsxs)("div",{id:"main-tabs",style:{display:"flex",gap:l?2:4,background:"rgba(255,255,255,0.08)",borderRadius:8,padding:l?2:3,flexWrap:l?"wrap":"nowrap"},children:[o.map(r=>{let o={...d,cursor:"pointer",...e===r.key?r.on:r.off},i=l&&r.short?r.short:r.label;return s?(0,t.jsx)("button",{type:"button",onClick:()=>s(r.key),style:o,children:i},r.key):(0,t.jsx)(n.default,{href:`/?mode=${r.key}`,style:{...o,display:"flex",alignItems:"center"},children:i},r.key)}),c,i.filter(e=>!r.IS_DEMO&&(!e.own||!a)).map(r=>{let o=e===r.key;return(0,t.jsxs)(n.default,{href:r.href,title:r.title,"aria-current":o?"page":void 0,style:{...d,display:"flex",alignItems:"center",gap:4,background:o?r.on:"rgba(255,255,255,0.06)",color:o?"#000":r.color},children:[r.label,r.badge&&(0,t.jsx)("span",{style:{fontSize:9,opacity:.7,marginLeft:2},children:r.badge})]},r.key)}),u]})}],26005);var l=e.i(71645),a=e.i(40996),s=e.i(68792);e.s(["default",0,function({isMobile:e=!1}){let r=(0,l.useSyncExternalStore)(s.watchAllDay,s.allDayNow,()=>!1),[o,i]=(0,l.useState)(()=>(0,a.scannerNow)()),[c,u]=(0,l.useState)(!1);if((0,l.useEffect)(()=>(0,a.watchScanner)(()=>{i((0,a.scannerNow)()),u(!1)}),[]),!r)return null;let d=o?.running&&String(o.action||"").startsWith("auto-bridge"),f={padding:e?"8px 12px":"8px 16px",borderRadius:6,fontSize:13,fontWeight:600,textDecoration:"none",display:"flex",alignItems:"center",gap:6,whiteSpace:"nowrap"};if(d)return(0,t.jsxs)(n.default,{href:"/setup",title:"Auto scan is running: see it on the Scan page",style:{...f,background:"rgba(0,255,136,0.15)",color:"#00ff88",border:"1px solid rgba(0,255,136,0.45)"},children:[(0,t.jsx)("span",{style:{width:7,height:7,borderRadius:"50%",background:"#00ff88"}}),"Auto scan"]});let p=o?.running||c;return(0,t.jsxs)("button",{onClick:()=>{u(!0),(0,s.startAutoScan)().catch(()=>u(!1))},disabled:p,title:p?"The scanner is busy: one scan at a time":"Start the all-day Auto-Bridge (experimental)",style:{...f,cursor:p?"default":"pointer",background:"rgba(255,255,255,0.04)",color:p?"#555":"#8b9a9a",border:"1px solid rgba(255,215,0,0.25)"},children:[(0,t.jsx)("span",{style:{width:7,height:7,borderRadius:"50%",background:"#556"}}),"Auto scan"]})}],76760)},28867,86106,e=>{"use strict";var t=e.i(71645),n=e.i(7214);let r=Object.freeze({known:!1,sent:new Set,mine:new Map,version:0}),o=new Set,i=r,l=null,a=new Map;function s(e=i.sent,t=i.known){for(let n of(i=Object.freeze({known:t,sent:e,mine:new Map(a),version:i.version+1}),[...o]))n()}function c(e){return o.add(e),l||i.known||(l=fetch("/api/outreach").then(e=>e.ok?e.json():null).then(e=>{e&&s(new Set((e.pending||[]).map(n.keyFor)),!0)}).catch(()=>{}).finally(()=>{l=null})),()=>o.delete(e)}function u(){return i}async function d(e){let t=await fetch("/api/outreach",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),n=await t.json().catch(()=>({}));if(!t.ok)throw Error(n.error||"Could not save that.");return n}async function f(e,{bridgeId:t}={}){let r=(0,n.keyFor)(e),o=a.has(r)?a.get(r):void 0;a.set(r,!0),s();try{return await d({action:"mark-sent",connectionId:e.id,profileUrl:e.profile_url,bridgeId:t})}catch(e){throw void 0===o?a.delete(r):a.set(r,o),s(),e}}async function p(e){let t=(0,n.keyFor)(e),r=a.has(t)?a.get(t):void 0;a.set(t,!1),s();try{return await d({action:"undo",connectionId:e.id,profileUrl:e.profile_url})}catch(e){throw void 0===r?a.delete(t):a.set(t,r),s(),e}}e.s(["REQUESTS_UNKNOWN",0,r,"hasRequest",0,function(e,t=i){if(!e)return!1;let r=(0,n.keyFor)(e);return t.mine.has(r)?t.mine.get(r):t.known?t.sent.has(r):"pending"===e.unlock_status||"sent"===e.outreach_status},"markRequested",0,f,"requestCount",0,function(e=i){let t=new Set(e.known?e.sent:[]);for(let[n,r]of e.mine)r?t.add(n):t.delete(n);return t.size},"requestsNow",0,u,"undoRequest",0,p,"watchRequests",0,c],86106),e.s(["default",0,function(){return(0,t.useSyncExternalStore)(c,u,()=>r)}],28867)},30215,e=>{"use strict";var t=e.i(71645),n=e.i(40996);e.s(["default",0,function(){return(0,t.useSyncExternalStore)(n.watchScanner,n.scannerNow,()=>n.SCANNER_UNKNOWN)}])},39160,84278,e=>{"use strict";var t=e.i(7214),n=e.i(7837);function r(e){let t=String(e||"").trim();if(!t)return null;let n=t.match(/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})(?::(\d{2}))?(?:\s*UTC)?$/);if(n)return Date.UTC(+n[1],n[2]-1,+n[3],+n[4],+n[5],+(n[6]||0));if(n=t.match(/^(\d{1,2})\/(\d{1,2})\/(\d{2,4}),?\s+(\d{1,2}):(\d{2})\s*(AM|PM)?$/i)){let e=100>+n[3]?2e3+ +n[3]:+n[3],t=n[4]%12;return"PM"===(n[6]||"").toUpperCase()&&(t+=12),Date.UTC(e,n[1]-1,+n[2],t,+n[5])}if(n=t.match(/^([A-Za-z]{3,9})\s+(\d{4})$/)){let e=Date.parse(`1 ${n[1]} ${n[2]} UTC`);return Number.isNaN(e)?null:e}let r=Date.parse(/\d{4}$/.test(t)?`${t} UTC`:t);return Number.isNaN(r)?null:r}let o=e=>(0,t.keyFor)({profile_url:e});function i(e=[]){let t=new Map;for(let n of e){let e=o(n["sender profile url"]),r=n["conversation id"];r&&e&&(t.has(e)||t.set(e,new Set),t.get(e).add(r))}let n=null;for(let[e,r]of t)(!n||r.size>t.get(n).size)&&(n=e);return n}let l=e=>{let t=f(e);return t?`name:${t}`:null},a=/sponsored/i,s=/in\s?mail/i,c=/linkedin\.com\/(company|showcase|school)\//i,u=e=>/^(yes|true|1)$/i.test(String(e||"").trim());function d(e,t){if(!e||!e.total)return"never";let n=t-e.last;return n<=2592e6||e.recent>=3?"warm":n<=31536e6?"cool":"dormant"}let f=e=>String(e||"").split(",")[0].normalize("NFD").replace(/[̀-ͯ]/g,"").toLowerCase().replace(/[^a-z0-9 ]+/g," ").replace(/\s+/g," ").trim();e.s(["buildConversations",0,function(e=[],{keepText:t=!1}={}){let n=i(e),d=new Map;for(let t of e)n&&o(t["sender profile url"])===n&&t.from&&d.set(t.from,(d.get(t.from)||0)+1);let p=f([...d].sort((e,t)=>t[1]-e[1])[0]?.[0]||""),g=e=>!!e&&(e===n||!!p&&e===`name:${p}`),m={},h=new Map,b=0,y=0;for(let n of e){let e=n["conversation id"],i=r(n.date);if(!e||null==i){y+=1;continue}if(u(n["is message draft"])){b+=1;continue}let d=o(n["sender profile url"])||l(n.from);d&&n.from&&!m[d]&&(m[d]=String(n.from).trim());let f=String(n["recipient profile urls"]||"").split(/[,;\s]+/).filter(Boolean).map(o),p=String(n.to||"").split(",").map(e=>e.trim()).filter(Boolean),x=f.length?f:p.map(l).filter(Boolean);f.length&&f.length===p.length&&f.forEach((e,t)=>{m[e]||(m[e]=p[t])}),f.length||p.forEach(e=>{let t=l(e);t&&!m[t]&&(m[t]=e)}),h.has(e)||h.set(e,{id:e,title:"",everyone:new Set,messages:[],kind:null,folder:"",newest:-1/0});let w=h.get(e);d&&w.everyone.add(d),x.forEach(e=>w.everyone.add(e)),w.kind=w.kind||function(e){let t=String(e.folder||""),n=String(e["conversation title"]||"");return a.test(t)||a.test(n)||c.test(String(e["sender profile url"]||""))?"sponsored":s.test(t)||s.test(n)?"inmail":null}(n),i>=w.newest&&(w.newest=i,w.folder=String(n.folder||"").trim().toLowerCase().slice(0,30));let S={t:i,fromMe:g(d)};t&&(S.text=[n.subject,n.content].map(e=>String(e||"").trim()).filter(Boolean).join("\n\n"),!w.title&&n["conversation title"]&&(w.title=String(n["conversation title"]).trim())),w.messages.push(S)}let x=[];for(let e of h.values()){let n=[...e.everyone].filter(e=>e&&!g(e)).sort();e.messages.sort((e,t)=>e.t-t.t);let r={id:e.id,people:n,group:n.length>1,kind:e.kind,folder:e.folder,messages:e.messages};t&&(r.title=e.title),x.push(r)}for(let e of Object.keys(m))g(e)&&delete m[e];return{self:n,conversations:x,names:m,drafts:b,skipped:y}},"buildInvitations",0,function(e=[],{keepText:t=!1}={}){let n=[];for(let i of e){let e=String(i.direction||"").toUpperCase();if("INCOMING"!==e&&"OUTGOING"!==e)continue;let a="INCOMING"===e?"in":"out",s=String(("in"===a?i.from:i.to)||"").trim(),c=o("in"===a?i.inviterprofileurl:i.inviteeprofileurl),u=(c&&/\/in\//.test(c)?c:null)||l(s);if(!u)continue;let d=r(i["sent at"]),f={id:`${a}|${u}|${d??""}`,dir:a,key:u,name:s.slice(0,120),t:d};t&&String(i.message||"").trim()&&(f.note=String(i.message).trim().slice(0,2e3)),n.push(f)}return n.sort((e,t)=>(t.t||0)-(e.t||0))},"careerChapters",0,function(e=[],t=[],n=Date.now()){let i=e.map(e=>({company:e["company name"]||"",title:e.title||"",from:r(e["started on"]),to:r(e["finished on"])})).filter(e=>e.company&&null!=e.from).sort((e,t)=>e.from-t.from),l=t.map(e=>({url:o(e.url||e.profile_url),t:r(e["connected on"]||e.connected_on)})).filter(e=>null!=e.t);return i.map(e=>{let t=e.to??n,r=l.filter(n=>n.t>=e.from&&n.t<t);return{...e,to:e.to,made:r.length,people:r.map(e=>e.url)}})},"conversationSummary",0,function(e){let t=e.messages[e.messages.length-1];return{id:e.id,people:e.people,group:e.group,kind:e.kind||null,folder:e.folder||"",last:t?t.t:null,lastFromThem:t?!t.fromMe:null,count:e.messages.length,mine:e.messages.filter(e=>e.fromMe).length}},"invitationSplit",0,function(e=[]){let t=0,n=0;for(let r of e){let e=String(r.direction||"").toUpperCase();"INCOMING"===e?t+=1:"OUTGOING"===e&&(n+=1)}return{incoming:t,outgoing:n,total:t+n}},"messageStats",0,function(e=[]){let t=i(e),n=new Map;for(let t of e){let e=o(t["sender profile url"]),i=t["conversation id"];if(!i||!e)continue;let l=String(t["recipient profile urls"]||"").split(/[,;\s]+/).filter(Boolean).map(o),a=r(t.date);if(null==a)continue;n.has(i)||n.set(i,{people:new Set,msgs:[]});let s=n.get(i);s.people.add(e),l.forEach(e=>s.people.add(e)),s.msgs.push({from:e,t:a})}let l=0,a=new Map;for(let e of n.values()){let n=[...e.people].filter(e=>e!==t);if(1!==n.length||!e.people.has(t))continue;let r=n[0],o=a.get(r)||{last:0,lastFromThem:!1,total:0,times:[]};for(let n of e.msgs)o.total+=1,o.times.push(n.t),n.t>=o.last&&(o.last=n.t,o.lastFromThem=n.from!==t),n.t>l&&(l=n.t);a.set(r,o)}for(let e of a.values())e.recent=e.times.filter(e=>e>l-7776e6).length,delete e.times;return{self:t,asOf:l||null,people:a}},"nameKey",0,f,"postingEffect",0,function(e=[],t=[]){let n=e.map(e=>({t:r(e.date),link:e.sharelink||e["share link"]||""})).filter(e=>null!=e.t).sort((e,t)=>e.t-t.t),o=t.map(e=>r(e["connected on"]||e.connected_on)).filter(e=>null!=e).sort((e,t)=>e-t);if(!o.length)return{posts:[],usualWeek:0,afterPosts:0};let i=Math.max(6048e5,o[o.length-1]-o[0]),l=o.length/i*6048e5,a=n.map(e=>({...e,week:o.filter(t=>t>=e.t&&t<e.t+6048e5).length})),s=a.length?a.reduce((e,t)=>e+t.week,0)/a.length:0;return{posts:a,usualWeek:l,afterPosts:s}},"readTable",0,function(e,t){let r=(0,n.parseCsv)(String(e||"")),o=r.findIndex(e=>e.some(e=>e.trim().toLowerCase()===t.toLowerCase()));if(o<0)return[];let i=r[o].map(e=>e.trim().toLowerCase());return r.slice(o+1).filter(e=>e.some(e=>e.trim())).map(e=>Object.fromEntries(i.map((t,n)=>[t,(e[n]??"").trim()])))},"warmthOf",0,d],84278);let p="six-degrees-galaxy-lab",g="six-degrees-galaxy-layouts",m=Object.freeze({on:!0,gravity:0,rings:1,push:15,pull:1,distance:1,dotSize:1,lines:1,sizeBy:"power",orbit:0,names:"key",branch:!1,colourBy:"tier",speed:15,loop:!1,labels:!0,v:2}),h=["gravity","rings","push","pull","distance","orbit","dotSize","lines","sizeBy","names","branch","colourBy","speed","loop"],b=["gravity","rings","push","pull","distance"],y=Object.freeze({gravity:.8,rings:0,push:6,pull:6,distance:.15,sizeBy:"links"}),x=Object.freeze({rings:{lines:1},orbit:{lines:1},clusters:{lines:3}}),w=Object.freeze({gravity:0,rings:3,push:6,pull:4,distance:.22,sizeBy:"power"}),S=new Set,k=null;function v(){return k||(k=function(){try{let e=JSON.parse(localStorage.getItem(p)||"null");if(!e||"object"!=typeof e)return m;return{...m,...e,...e.v===m.v?{}:{branch:!1},v:m.v,on:!0}}catch{return m}}()),k}function j(e){k=null===e?{...m,labels:v().labels}:{...v(),...e,on:!0};try{localStorage.setItem(p,JSON.stringify(k))}catch{}S.forEach(e=>e())}let C=null,F=new Set;function N(){if(!C){try{C=JSON.parse(localStorage.getItem(g)||"[]")}catch{C=[]}Array.isArray(C)||(C=[])}return C}function M(e){C=e;try{localStorage.setItem(g,JSON.stringify(e))}catch{}F.forEach(e=>e())}let O=new Set,E={at:null,playing:!1,min:null,max:null,of:0,find:"",fly:0,found:0,fit:0,social:void 0},I=null;function D(e){let t={...E,...e};Object.keys(e).every(e=>Object.is(t[e],E[e]))||(E=t,O.forEach(e=>e()))}function B(){cancelAnimationFrame(I),I=null,D({playing:!1})}let T=null;function z(e){let n=new Map;for(let[r,o]of Object.entries(e?.people||{}))n.set((0,t.keyFor)({profile_url:r}),{...o});for(let[r,o]of Object.entries(e?.live||{})){let e=(0,t.keyFor)({profile_url:r}),i=n.get(e)||{total:0,recent:0};o.last&&(!i.last||o.last>i.last)&&(i.last=o.last,"boolean"==typeof o.lastFromThem&&(i.lastFromThem=o.lastFromThem)),i.unread=o.unread,i.total=Math.max(i.total||0,1),n.set(e,i)}let r=Math.max(e?.asOf||0,e?.liveAt&&Date.parse(e.liveAt)||0);for(let e of n.values())(e.last||0)>r&&(r=e.last);return{merged:n,asOf:r,chapters:e?.chapters||[],posts:e?.posts?.posts||[]}}let L={1:"#4FC3F7",2:"#BA68C8",3:"#FFB74D"},R=["#FF6B6B","#4ECDC4","#FFD93D","#A29BFE","#00B894","#FD79A8","#74B9FF","#E17055"],A={warm:"#FF7043",cool:"#4FC3F7",dormant:"#8D6E63",never:"#546E7A"},$="#4a5568",U=["#2b1b6b","#6a1b9a","#c2185b","#f4511e","#ffb300","#fff3b0"];function W(e){let t=Math.max(0,Math.min(1,Number(e)||0))*(U.length-1),n=Math.min(U.length-2,Math.floor(t)),r=t-n,o=U[n],i=U[n+1];return`#${[1,3,5].map(e=>Math.round(parseInt(o.slice(e,e+2),16)*(1-r)+parseInt(i.slice(e,e+2),16)*r).toString(16).padStart(2,"0")).join("")}`}function _(e=[],t=()=>0){let n=[...new Set(e.map(t))].sort((e,t)=>e-t),r=new Map(n.map((e,t)=>[e,n.length>1?t/(n.length-1):1]));return e=>r.get(t(e))??0}let q=e=>String(e||"").trim().toLowerCase();e.s(["CLUSTERS",0,y,"FORCE_KEYS",0,b,"LAB_DEFAULTS",0,m,"LAYOUT_LOOKS",0,x,"ORBIT",0,w,"applyLayout",0,function(e){let t=N().find(t=>t.name===e);t&&j(Object.fromEntries(h.filter(e=>e in t.settings).map(e=>[e,t.settings[e]])))},"bornTimes",0,function(e,t){let n=new Map(e.map(e=>[e.id,e])),r=new Map,o=(e,i=0)=>{if(r.has(e.id))return r.get(e.id);let l=-1/0,a=t.get(e.id);if(1===e.degree||null==a||i>6){let t=e.connected_date?Date.parse(e.connected_date):NaN;l=Number.isNaN(t)?-1/0:t}return null!=a&&n.has(a)&&i<=6&&(l=Math.max(l,o(n.get(a),i+1))),r.set(e.id,l),l};return e.map(e=>o(e))},"chapterAt",0,function(e,t){let n=null;for(let r of e?.chapters||[])Number.isFinite(r.from)&&r.from<=t&&(!n||r.from>n.from)&&(n=r);return n&&(!Number.isFinite(n.to)||n.to>t)?n:null},"clockNow",0,()=>E,"colourScheme",0,function(e,n,r,o){if("degree"===e)return{of:e=>L[Math.min(e.degree||1,3)],legend:[["1st",L[1]],["2nd",L[2]],["3rd+",L[3]]]};if("company"===e){let e=new Map;for(let t of n){let n=q(t.company);if(!n)continue;let r=e.get(n)||{name:String(t.company).trim(),n:0};r.n+=1,e.set(n,r)}let t=[...e.entries()].filter(([,e])=>e.n>1).sort((e,t)=>t[1].n-e[1].n).slice(0,R.length),r=new Map(t.map(([e],t)=>[e,R[t]]));return{of:e=>r.get(q(e.company))||$,legend:[...t.map(([,e],t)=>[e.name,R[t]]),["Other",$]]}}if("heat"===e){let e=function(e=[]){return _(e,t.score)}(n);return{of:t=>W(e(t)),heat:e,legend:[["Cold",W(.1)],["Warm",W(.45)],["Hot",W(.75)],["Hottest",W(1)]]}}return"warmth"===e&&o?{of:e=>1===(e.degree||1)?A[d(o.merged.get((0,t.keyFor)(e)),o.asOf)]:"#2d3748",legend:[["Warm",A.warm],["Cool",A.cool],["Dormant",A.dormant],["Never messaged",A.never]]}:{of:e=>r[e.tier]||"#666",legend:Object.entries(r)}},"effectiveLab",0,e=>e.on?e:e.labels===m.labels?m:{...m,labels:e.labels},"findMatches",0,function(e,t){let n=String(t||"").trim().toLowerCase();if(n.length<2)return null;let r=new Set;for(let t of e)[t.name,t.company,t.role,t.headline].some(e=>e&&String(e).toLowerCase().includes(n))&&r.add(t.id);return r},"forgetLayout",0,function(e){M(N().filter(t=>t.name!==e))},"heatBy",0,_,"heatColour",0,W,"labNow",0,v,"layoutOf",0,function(e){let t=t=>Object.entries(t).every(([t,n])=>e[t]===n);return b.every(t=>e[t]===m[t])?"rings":t(y)?"clusters":t(w)?"orbit":null},"layoutsNow",0,N,"loadSocial",0,function(){return T||(T=fetch("/api/social").then(e=>e.ok?e.json():{}).then(e=>{D({social:e.social?z(e.social):null})}).catch(()=>D({social:null}))),T},"mergeSocial",0,z,"milestones",0,function(e){let t=[];for(let n of e?.chapters||[])Number.isFinite(n.from)&&t.push({t:n.from,kind:"job",label:n.company});for(let n of e?.posts||[])Number.isFinite(n.t)&&t.push({t:n.t,kind:"post",label:"You posted"});return t.sort((e,t)=>e.t-t.t)},"pause",0,B,"play",0,function e(){if(null==E.min||null==E.max||E.max<=E.min)return;let t=E.max-E.min,n=1e3*Math.max(1,Number(v().speed)||15),r=null==E.at||E.at>=E.max?E.min:E.at,o=performance.now()-(r-E.min)/t*n;D({at:r,playing:!0});let i=r=>{let l=Math.min(E.max,E.min+(r-o)/n*t);if(D({at:l}),l>=E.max){I=null,D({playing:!1}),v().loop&&setTimeout(()=>{I||E.at!==E.max||(D({at:E.min}),e())},1200);return}I=requestAnimationFrame(i)};cancelAnimationFrame(I),I=requestAnimationFrame(i)},"reachCounts",0,function(e,t){let n=new Map(e.map(e=>[e.id,0]));for(let r of e){let e=t.get(r.id);for(let r=0;null!=e&&r<7;r++)n.set(e,(n.get(e)||0)+1),e=t.get(e)}return n},"saveLayout",0,function(e){let t=String(e||"").trim().slice(0,40);if(!t)return;let n=v(),r=Object.fromEntries(h.map(e=>[e,n[e]]));M([...N().filter(e=>e.name!==t),{name:t,settings:r}])},"setClock",0,D,"setLab",0,j,"stopReplay",0,function(){B(),D({at:null})},"watchClock",0,function(e){return O.add(e),()=>O.delete(e)},"watchLab",0,function(e){S.add(e);let t=t=>{t.key===p&&(k=null,e())};return window.addEventListener("storage",t),()=>{S.delete(e),window.removeEventListener("storage",t)}},"watchLayouts",0,function(e){return F.add(e),()=>F.delete(e)}],39160)}]);
|
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.6.0] - 2026-10-03
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Themes** (Settings → Appearance). Seven looks: Standard, High contrast, Obsidian, Glass,
|
|
13
|
+
Analyst, Space and Synthwave, each with a small preview of the map in its colours. Then make it
|
|
14
|
+
yours: the background and a second colour, a backdrop behind everything (stars, a grid, a glow,
|
|
15
|
+
a sunset horizon), the accent, each tier's dot and your own, the map's lines (in the colour of
|
|
16
|
+
the person they lead to, or one colour), how see-through the side panels are, and the font.
|
|
17
|
+
Everything changes as you go, on every page, and stays in this browser. Copy a theme code to
|
|
18
|
+
share a look; pasting one only ever sets colours and choices.
|
|
19
|
+
- **Clusters, the way Obsidian draws a graph** (Network Circle → Filters → Layout → Clusters). Your
|
|
20
|
+
network as one round disc: each connection with a scanned circle is a big hub with their circle
|
|
21
|
+
around them, and everyone is sized by their lines. Someone in several circles is linked to each
|
|
22
|
+
of them, so they sit between those hubs and tie them together. The forces have Obsidian's names,
|
|
23
|
+
Center force, Repel force, Link force and Link distance, each saying what it does to your
|
|
24
|
+
network. Names fade as you zoom out, all but the hubs'. Picking a layout fits everyone on screen,
|
|
25
|
+
and so does ⤢ Fit.
|
|
26
|
+
- **Orbit** (Filters → Forces): sets the whole map turning round you, every circle with it, so
|
|
27
|
+
nothing loses its shape. Still under Reduce Motion.
|
|
28
|
+
- **Dots along the header's line while a scan runs**: one for each page the scanner reads,
|
|
29
|
+
collecting from the left, on every tab. A click opens the Scan page.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- **The same header on every tab.** The tabs sit in the exact middle of the window, and Settings,
|
|
33
|
+
notifications, ↻ Check for new and your level are on Paths, Outlink and Scan too. A notification
|
|
34
|
+
picked on another tab opens on the map.
|
|
35
|
+
- **Separation fits a laptop's window.** The header is slimmer: the search sits in the title's row,
|
|
36
|
+
the tier line stays without the sentence under it (it's the title's tooltip now), Only show sits
|
|
37
|
+
beside the tiers, the pill saying where the slider is sits over its middle, and the long
|
|
38
|
+
explanations moved into tooltips. The cards are packed a little closer, so the whole map, ten
|
|
39
|
+
people and every way in, shows at once in the app's normal window wherever the slider is.
|
|
40
|
+
- **The Scan page shows every step at once**, down a line: Get ready, Sign in, Who you know, Who
|
|
41
|
+
they know, each with its label, what it does and its one button, marked Done, Now or Next. The
|
|
42
|
+
line turns green as each is done, so it's plain what's finished and what's left; no more tapping
|
|
43
|
+
dots to see a step.
|
|
44
|
+
- **↻ Check for new**, in words, in the map's header (it was a bare ↻): the same name as the Scan
|
|
45
|
+
page's button, for checking your LinkedIn for new connections in one click.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
- **Separation's tiers choose who's ranked, not who leads to them.** Hiding a tier in Filters also
|
|
49
|
+
hid your connections of that tier (the purple A-tier lines), so the people they lead to lost a
|
|
50
|
+
way in. In Separation every connection now stays a way in, whatever its tier.
|
|
51
|
+
- The level in the round button at the top right matches the Profile page's: it left out C and D
|
|
52
|
+
tiers and catalysts, so it read lower.
|
|
53
|
+
|
|
54
|
+
## [0.5.1-beta.1] - 2026-10-02 (beta: a pre-release, never installed automatically)
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
- **Six Degrees for Windows and Linux (beta).** The same app as on the Mac, with the scanner's
|
|
58
|
+
own Python inside, so nothing needs installing before a scan. **Windows:**
|
|
59
|
+
*Six-Degrees-Windows-Setup.exe* installs it for you with one click (no admin rights), with Start
|
|
60
|
+
Menu and Desktop shortcuts, and opens it; running a newer one over it updates it. It isn't
|
|
61
|
+
signed yet, so SmartScreen may ask the first time (*More info → Run anyway*). **Linux:** a
|
|
62
|
+
`.deb` (`sudo apt install ./Six-Degrees-Linux-x64.deb`) or a `.tar.gz`. Scanning needs Google
|
|
63
|
+
Chrome on both. Your network stays in your home folder's `.six-degrees`, as on the Mac;
|
|
64
|
+
uninstalling never touches it. Settings → Updates points to the new installer there.
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
- On Windows the scanner would have stopped after its first batch: its progress lines
|
|
68
|
+
("→") can't be written in Windows' default text encoding. Its Python now always writes UTF-8.
|
|
69
|
+
|
|
9
70
|
## [0.5.0] - 2026-10-02
|
|
10
71
|
|
|
11
72
|
### Added
|
package/README.md
CHANGED
|
@@ -41,22 +41,24 @@ know placed on rings by how much they can open up for you, the handful of people
|
|
|
41
41
|
whose own circles reach the furthest, and the shortest chain from you to a stranger
|
|
42
42
|
worth meeting.
|
|
43
43
|
|
|
44
|
-
It's a Mac app
|
|
44
|
+
It's a Mac app, and now a Windows and Linux app too (beta). It runs on your computer, against your own data, with no account and
|
|
45
45
|
no server.
|
|
46
46
|
|
|
47
47
|
## Download
|
|
48
48
|
|
|
49
49
|
<p align="center">
|
|
50
50
|
<a href="https://github.com/blakeb056/six-degrees/releases/latest/download/Six-Degrees-Mac-Apple-Silicon.dmg"><img src="docs/img/download-apple-silicon.png" width="346" alt="Download Six Degrees for a Mac with Apple Silicon (M1 or newer)"></a>
|
|
51
|
-
<a href="https://github.com/blakeb056/six-degrees/releases/latest/download/Six-Degrees-Mac-Intel.dmg"><img src="docs/img/download-intel.png" width="346" alt="Download Six Degrees for a Mac with an Intel chip"></a>
|
|
51
|
+
<a href="https://github.com/blakeb056/six-degrees/releases/latest/download/Six-Degrees-Mac-Intel.dmg"><img src="docs/img/download-intel.png" width="346" alt="Download Six Degrees for a Mac with an Intel chip"></a><br>
|
|
52
|
+
<a href="https://github.com/blakeb056/six-degrees/releases/download/v0.5.1-beta.1/Six-Degrees-Windows-Setup.exe"><img src="docs/img/download-windows.png" width="346" alt="Download Six Degrees for Windows 10 or 11 (beta): one-click Setup.exe"></a>
|
|
53
|
+
<a href="https://github.com/blakeb056/six-degrees/releases/download/v0.5.1-beta.1/Six-Degrees-Linux-x64.deb"><img src="docs/img/download-linux.png" width="346" alt="Download Six Degrees for Linux (beta): .deb for Ubuntu and Debian"></a>
|
|
52
54
|
</p>
|
|
53
55
|
|
|
54
56
|
| Platform | Download | Needs |
|
|
55
57
|
|---|---|---|
|
|
56
58
|
| **macOS**, Apple Silicon (M1 or newer) | [Six-Degrees-Mac-Apple-Silicon.dmg](https://github.com/blakeb056/six-degrees/releases/latest/download/Six-Degrees-Mac-Apple-Silicon.dmg) | macOS 13.5 (Ventura) or later |
|
|
57
59
|
| **macOS**, Intel | [Six-Degrees-Mac-Intel.dmg](https://github.com/blakeb056/six-degrees/releases/latest/download/Six-Degrees-Mac-Intel.dmg) | macOS 13.5 (Ventura) or later |
|
|
58
|
-
| **
|
|
59
|
-
| **
|
|
60
|
+
| **Windows** (beta) | [Six-Degrees-Windows-Setup.exe](https://github.com/blakeb056/six-degrees/releases/download/v0.5.1-beta.1/Six-Degrees-Windows-Setup.exe) | Windows 10 or 11, 64-bit |
|
|
61
|
+
| **Linux** (beta) | [Six-Degrees-Linux-x64.deb](https://github.com/blakeb056/six-degrees/releases/download/v0.5.1-beta.1/Six-Degrees-Linux-x64.deb) or [.tar.gz](https://github.com/blakeb056/six-degrees/releases/download/v0.5.1-beta.1/Six-Degrees-Linux-x64.tar.gz), or from the Terminal: `npx six-degrees` ([how](https://sixdegreesapp.com/download/#linux)) | Ubuntu or Debian, 64-bit (npx: Node 22.13 or later) |
|
|
60
62
|
|
|
61
63
|
<sub>Every release is on [GitHub Releases](https://github.com/blakeb056/six-degrees/releases) with a
|
|
62
64
|
`SHA256SUMS` file ([latest](https://github.com/blakeb056/six-degrees/releases/latest/download/SHA256SUMS)); the Terminal install below checks the download against it.
|
|
@@ -114,7 +116,20 @@ touch it. Each new version also copies your data into its `backups` folder befor
|
|
|
114
116
|
first opens it. To take it to another computer, see
|
|
115
117
|
[Moving to a new computer](#moving-to-a-new-computer).
|
|
116
118
|
|
|
117
|
-
**
|
|
119
|
+
**Windows (beta):** download [Six-Degrees-Windows-Setup.exe](https://github.com/blakeb056/six-degrees/releases/download/v0.5.1-beta.1/Six-Degrees-Windows-Setup.exe)
|
|
120
|
+
and open it. It installs for you alone (no administrator password), adds Start Menu and
|
|
121
|
+
Desktop shortcuts, and opens Six Degrees. It isn't signed with a paid certificate yet, so
|
|
122
|
+
SmartScreen may say it protected your PC: **More info → Run anyway**. A newer Setup.exe
|
|
123
|
+
updates it in place; your network stays in `%USERPROFILE%\.six-degrees`, which
|
|
124
|
+
uninstalling never touches. Scanning needs Google Chrome.
|
|
125
|
+
|
|
126
|
+
**Linux app (beta):** `sudo apt install ./Six-Degrees-Linux-x64.deb` with the
|
|
127
|
+
[.deb](https://github.com/blakeb056/six-degrees/releases/download/v0.5.1-beta.1/Six-Degrees-Linux-x64.deb), then open it from your apps. The
|
|
128
|
+
[.tar.gz](https://github.com/blakeb056/six-degrees/releases/download/v0.5.1-beta.1/Six-Degrees-Linux-x64.tar.gz) runs from its folder; on Ubuntu 24.04 and
|
|
129
|
+
later run `sudo chown root:root chrome-sandbox && sudo chmod 4755 chrome-sandbox` in it
|
|
130
|
+
once (or start it with `--no-sandbox`). Scanning needs Google Chrome (not Chromium).
|
|
131
|
+
|
|
132
|
+
**Linux from the Terminal:** install **Node 22.13 or later** (from nodejs.org or nvm; Ubuntu's own
|
|
118
133
|
`nodejs` package is too old), then run:
|
|
119
134
|
|
|
120
135
|
```bash
|
|
@@ -131,8 +146,7 @@ up the scanner** button downloads a private copy into your data folder (24–33
|
|
|
131
146
|
GitHub, checked against a checksum built into Six Degrees). It's tested on Ubuntu, and
|
|
132
147
|
works on a Mac too if you'd rather not install the app.
|
|
133
148
|
|
|
134
|
-
|
|
135
|
-
Windows yet; npm says so if you try.
|
|
149
|
+
`npx six-degrees` doesn't run on Windows; use the Windows app above.
|
|
136
150
|
|
|
137
151
|
<details>
|
|
138
152
|
<summary>Run it from source (contributors)</summary>
|
|
@@ -248,7 +262,7 @@ two steps away, and 503 of them, 84%, are reached through only one connection.
|
|
|
248
262
|
| Any network, any data, research-grade statistics | — (LinkedIn networks only) | — | any data | ✓ what they're built for |
|
|
249
263
|
| Runs on your computer, with no account | ✓ | — (online, with your account) | varies | ✓ |
|
|
250
264
|
| Price | free, MIT | free, with paid plans | free to paid | free, open source |
|
|
251
|
-
| Windows |
|
|
265
|
+
| Windows | ✓ (beta) | ✓ | ✓ | ✓ |
|
|
252
266
|
|
|
253
267
|
Something out of date, or unfair to another tool? [Open an issue](https://github.com/blakeb056/six-degrees/issues).
|
|
254
268
|
|
package/lib/data-import.js
CHANGED
|
@@ -869,6 +869,7 @@ export function restartAdvice({ kind, code, dataDir = null }) {
|
|
|
869
869
|
return { canRestart: true, how: 'Click Restart now to finish it: Six Degrees restarts, which takes a few seconds, and finishes the import as it starts.' };
|
|
870
870
|
}
|
|
871
871
|
if (kind === 'mac-app') return { canRestart: false, how: `Quit Six Degrees (⌘Q) and open it again. ${finish}` };
|
|
872
|
+
if (kind === 'windows-app' || kind === 'linux-app') return { canRestart: false, how: `Quit Six Degrees (File → Exit) and open it again. ${finish}` };
|
|
872
873
|
if (kind === 'npm') {
|
|
873
874
|
const flag = dataDir ? ` --data-dir '${String(dataDir).replace(/'/g, `'\\''`)}'` : '';
|
|
874
875
|
return {
|
package/lib/galaxy-export.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// like any other download.
|
|
5
5
|
|
|
6
6
|
import { clockNow, setClock, play, pause } from './galaxy-lab.js';
|
|
7
|
+
import { MAP_LOOK } from './themes.js';
|
|
7
8
|
|
|
8
9
|
let current = null; // { svg, stamp: () => text or null, legend: () => [[label, colour]] }
|
|
9
10
|
|
|
@@ -41,7 +42,7 @@ function svgImage(svg) {
|
|
|
41
42
|
// One frame: the Galaxy, its date while a replay runs, the legend, and a small credit.
|
|
42
43
|
async function drawFrame(ctx, w, h) {
|
|
43
44
|
const img = await svgImage(current.svg);
|
|
44
|
-
ctx.fillStyle = '
|
|
45
|
+
ctx.fillStyle = MAP_LOOK.bg; // the theme's colour (lib/themes.js)
|
|
45
46
|
ctx.fillRect(0, 0, w, h);
|
|
46
47
|
ctx.drawImage(img, 0, 0, w, h);
|
|
47
48
|
const scale = w / current.svg.clientWidth;
|
package/lib/galaxy-lab.js
CHANGED
|
@@ -23,7 +23,8 @@ export const LAB_DEFAULTS = Object.freeze({
|
|
|
23
23
|
distance: 1, // how far out those people sit
|
|
24
24
|
dotSize: 1,
|
|
25
25
|
lines: 1,
|
|
26
|
-
sizeBy: 'power', // 'power' (power score) or 'reach' (how many hang off them)
|
|
26
|
+
sizeBy: 'power', // 'power' (power score), 'links' (how many lines meet at them, as Obsidian sizes a note) or 'reach' (how many hang off them)
|
|
27
|
+
orbit: 0, // the whole map turning round you, in tenths of a radian a second; 0 holds still
|
|
27
28
|
names: 'key', // 'key' (you, S tier, catalysts) or 'all' (all your connections)
|
|
28
29
|
branch: false, // hovering a dot lights up its branch and dims the rest: off to start (Blake, 2026-10-02: it glitched)
|
|
29
30
|
colourBy: 'tier', // 'tier', 'heat' (power as a thermal map), 'degree', 'company' or 'warmth' (from the Social tab)
|
|
@@ -34,10 +35,24 @@ export const LAB_DEFAULTS = Object.freeze({
|
|
|
34
35
|
});
|
|
35
36
|
|
|
36
37
|
/** The slider settings a saved layout keeps (not whether the lab is on, nor the names switch). */
|
|
37
|
-
const LAYOUT_KEYS = ['gravity', 'rings', 'push', 'pull', 'distance', 'dotSize', 'lines', 'sizeBy', 'names', 'branch', 'colourBy', 'speed', 'loop'];
|
|
38
|
+
const LAYOUT_KEYS = ['gravity', 'rings', 'push', 'pull', 'distance', 'orbit', 'dotSize', 'lines', 'sizeBy', 'names', 'branch', 'colourBy', 'speed', 'loop'];
|
|
38
39
|
|
|
39
|
-
/**
|
|
40
|
-
export const
|
|
40
|
+
/** The forces a layout sets, and the ones the Galaxy re-settles for when they change. */
|
|
41
|
+
export const FORCE_KEYS = ['gravity', 'rings', 'push', 'pull', 'distance'];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Clusters, the way Obsidian draws a vault (Blake, 2026-10-03: "like obsidian
|
|
45
|
+
* where it showing the nodes of the clusters all connecting back but retaining
|
|
46
|
+
* a circular look but the nodes need to be a big thing"): no rings, a center
|
|
47
|
+
* force strong enough to keep the whole network a round disc, each connection's
|
|
48
|
+
* circle held round them by its links, everyone sized by how many lines meet at
|
|
49
|
+
* them, so a connection with a scanned circle is a big hub. Someone in several
|
|
50
|
+
* circles is linked to each, so they sit between those hubs and tie them together.
|
|
51
|
+
*/
|
|
52
|
+
export const CLUSTERS = Object.freeze({ gravity: 0.8, rings: 0, push: 6, pull: 6, distance: 0.15, sizeBy: 'links' });
|
|
53
|
+
|
|
54
|
+
/** What picking a layout does to the look, without being part of what makes it that layout. */
|
|
55
|
+
export const LAYOUT_LOOKS = Object.freeze({ rings: { lines: 1 }, orbit: { lines: 1 }, clusters: { lines: 3 } });
|
|
41
56
|
|
|
42
57
|
/**
|
|
43
58
|
* Orbit, the old view, as a layout of the Galaxy (Blake, 2026-10-02: "add that as a
|
|
@@ -50,7 +65,7 @@ export const ORBIT = Object.freeze({ gravity: 0, rings: 3, push: 6, pull: 4, dis
|
|
|
50
65
|
/** Which layout the sliders are set to: 'rings', 'clusters', 'orbit', or null for your own. */
|
|
51
66
|
export function layoutOf(lab) {
|
|
52
67
|
const is = (preset) => Object.entries(preset).every(([k, v]) => lab[k] === v);
|
|
53
|
-
if (
|
|
68
|
+
if (FORCE_KEYS.every((k) => lab[k] === LAB_DEFAULTS[k])) return 'rings';
|
|
54
69
|
if (is(CLUSTERS)) return 'clusters';
|
|
55
70
|
if (is(ORBIT)) return 'orbit';
|
|
56
71
|
return null;
|
|
@@ -123,10 +138,11 @@ export function forgetLayout(name) { writeLayouts(layoutsNow().filter((l) => l.n
|
|
|
123
138
|
// `max` are the first and last dates in the network on screen, set by the Galaxy
|
|
124
139
|
// (both null when it has no dates, as in the sample network).
|
|
125
140
|
// The same store holds the lab's other passing state: what Find is looking
|
|
126
|
-
// for (and a count that asks the Galaxy to fly to the first match),
|
|
127
|
-
//
|
|
141
|
+
// for (and a count that asks the Galaxy to fly to the first match), a count
|
|
142
|
+
// that asks it to fit everyone on screen once it settles, and the Social tab's
|
|
143
|
+
// data once it's loaded.
|
|
128
144
|
const clockListeners = new Set();
|
|
129
|
-
let clock = { at: null, playing: false, min: null, max: null, of: 0, find: '', fly: 0, found: 0, social: undefined };
|
|
145
|
+
let clock = { at: null, playing: false, min: null, max: null, of: 0, find: '', fly: 0, found: 0, fit: 0, social: undefined };
|
|
130
146
|
let raf = null;
|
|
131
147
|
|
|
132
148
|
export const clockNow = () => clock;
|
package/lib/level.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Your network's level: the number in the round button at the top right, and
|
|
2
|
+
// the Profile page's Network Power. One formula for both: the header used to
|
|
3
|
+
// leave out C and D tiers and catalysts, so it showed a lower level than the
|
|
4
|
+
// Profile page it opens.
|
|
5
|
+
|
|
6
|
+
/** Network Power, as the Profile page explains it. */
|
|
7
|
+
export function networkPower(degree1 = [], degree2 = []) {
|
|
8
|
+
const tiers = { S: 0, A: 0, B: 0, C: 0, D: 0 };
|
|
9
|
+
for (const c of degree1) tiers[c.tier] = (tiers[c.tier] || 0) + 1;
|
|
10
|
+
let d2S = 0;
|
|
11
|
+
for (const c of degree2) if (c.tier === 'S') d2S += 1;
|
|
12
|
+
const clusters = new Set(degree2.map((c) => c.source_connection_id).filter(Boolean)).size;
|
|
13
|
+
const catalysts = degree1.filter((c) => c.is_catalyst).length;
|
|
14
|
+
return tiers.S * 100 + tiers.A * 40 + tiers.B * 15 + tiers.C * 5 + tiers.D * 1
|
|
15
|
+
+ clusters * 200 + catalysts * 150 + d2S * 50 + degree1.length;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The level a Network Power reaches: level n takes 10·n² power. */
|
|
19
|
+
export const levelOf = (power) => Math.floor(Math.sqrt(Math.max(0, power) / 10));
|
|
20
|
+
|
|
21
|
+
export const networkLevel = (degree1, degree2) => levelOf(networkPower(degree1, degree2));
|
|
22
|
+
|
|
23
|
+
// Remembered for this tab, so a page that doesn't load the network (Scan, say)
|
|
24
|
+
// can show the same number without loading it. One per network: your own, the
|
|
25
|
+
// sample, or a CSV. A small store, for useSyncExternalStore.
|
|
26
|
+
const KEY = 'six-degrees-level';
|
|
27
|
+
const known = {};
|
|
28
|
+
const listeners = new Set();
|
|
29
|
+
|
|
30
|
+
export function watchLevel(f) {
|
|
31
|
+
listeners.add(f);
|
|
32
|
+
return () => listeners.delete(f);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function levelNow(source = 'own') {
|
|
36
|
+
if (!(source in known)) {
|
|
37
|
+
try {
|
|
38
|
+
const raw = sessionStorage.getItem(`${KEY}:${source}`);
|
|
39
|
+
const n = raw == null ? NaN : Number(raw);
|
|
40
|
+
known[source] = Number.isFinite(n) ? n : null;
|
|
41
|
+
} catch { known[source] = null; }
|
|
42
|
+
}
|
|
43
|
+
return known[source];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function rememberLevel(level, source = 'own') {
|
|
47
|
+
if (levelNow(source) === level) return;
|
|
48
|
+
known[source] = level;
|
|
49
|
+
try { sessionStorage.setItem(`${KEY}:${source}`, String(level)); } catch { /* private window: it's loaded again instead */ }
|
|
50
|
+
listeners.forEach((f) => f());
|
|
51
|
+
}
|
package/lib/release.js
CHANGED
|
@@ -26,20 +26,26 @@ export function compareVersions(a, b) {
|
|
|
26
26
|
return 0;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/** The desktop apps that update by running the new installer, not by replacing themselves (DESKTOP.md D3). */
|
|
30
|
+
export const INSTALLER_KINDS = Object.freeze(['windows-app', 'linux-app']);
|
|
31
|
+
|
|
29
32
|
/**
|
|
30
|
-
* 'mac-app', 'source' (a git checkout run with
|
|
31
|
-
* 'npm'. The launchers say which
|
|
32
|
-
*
|
|
33
|
+
* 'mac-app', 'windows-app', 'linux-app', 'source' (a git checkout run with
|
|
34
|
+
* `npm run start:packaged`) or 'npm'. The launchers say which
|
|
35
|
+
* (SIX_DEGREES_INSTALL); an older Mac app that predates that is recognised by
|
|
36
|
+
* where it is running from, on a Mac only.
|
|
33
37
|
*/
|
|
34
|
-
export function installKind(env = process.env, root = '') {
|
|
35
|
-
if (['mac-app', 'source', 'npm'].includes(env.SIX_DEGREES_INSTALL)) {
|
|
38
|
+
export function installKind(env = process.env, root = '', platform = process.platform) {
|
|
39
|
+
if (['mac-app', 'source', 'npm', ...INSTALLER_KINDS].includes(env.SIX_DEGREES_INSTALL)) {
|
|
36
40
|
return env.SIX_DEGREES_INSTALL;
|
|
37
41
|
}
|
|
38
|
-
return /\.app\/Contents\/Resources/.test(root || '') ? 'mac-app' : 'npm';
|
|
42
|
+
return platform === 'darwin' && /\.app\/Contents\/Resources/.test(root || '') ? 'mac-app' : 'npm';
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
/** What to run to update, for each way of installing. */
|
|
45
|
+
/** What to run to update, for each way of installing (null: download the installer instead). */
|
|
42
46
|
export function updateCommand(kind, slug, { dataDir } = {}) {
|
|
47
|
+
// Windows and Linux: no Terminal line; the release page's installer replaces the app.
|
|
48
|
+
if (INSTALLER_KINDS.includes(kind)) return null;
|
|
43
49
|
if (kind === 'mac-app') {
|
|
44
50
|
return `curl -fsSL https://raw.githubusercontent.com/${slug}/main/install.sh | bash`;
|
|
45
51
|
}
|
package/lib/scanner-python.js
CHANGED
|
@@ -38,7 +38,7 @@ import { SETUP_WORK_PREFIX } from './data-folder.js';
|
|
|
38
38
|
* download instead of 104 (on a Mac, stripping saves 0.2 MB).
|
|
39
39
|
*
|
|
40
40
|
* To move to a newer one: take the new release's SHA256SUMS lines for these
|
|
41
|
-
*
|
|
41
|
+
* five names, and each file's size, and change all of them together.
|
|
42
42
|
*/
|
|
43
43
|
export const STANDALONE_PYTHON = Object.freeze({
|
|
44
44
|
release: '20260814',
|
|
@@ -64,6 +64,13 @@ export const STANDALONE_PYTHON = Object.freeze({
|
|
|
64
64
|
sha256: '2d8e17dfd732102cfeb18e0e1fa6769b24caa034e159981129590fe409c7157a',
|
|
65
65
|
size: 29217771,
|
|
66
66
|
}),
|
|
67
|
+
// Windows (DESKTOP.md D3): the desktop app bundles it; its layout differs
|
|
68
|
+
// (python.exe at the top, no bin/), see standalonePython().
|
|
69
|
+
'win32-x64': Object.freeze({
|
|
70
|
+
file: 'cpython-3.12.14+20260814-x86_64-pc-windows-msvc-install_only_stripped.tar.gz',
|
|
71
|
+
sha256: '89f18f6932917163b74339ebcec2645c8e47ae7f1c5f2ac37f2b4f4cf3beb647',
|
|
72
|
+
size: 21976599,
|
|
73
|
+
}),
|
|
67
74
|
}),
|
|
68
75
|
});
|
|
69
76
|
|
|
@@ -77,7 +84,7 @@ export function standaloneUrl(build, pin = STANDALONE_PYTHON) {
|
|
|
77
84
|
|
|
78
85
|
/**
|
|
79
86
|
* This computer, as a key of STANDALONE_PYTHON.builds, or null when there is
|
|
80
|
-
* no
|
|
87
|
+
* no download for it here (Windows, 32-bit, other chips).
|
|
81
88
|
*
|
|
82
89
|
* On a Mac the chip is the hardware's (`hw.optional.arm64`, as install.sh and
|
|
83
90
|
* the updater ask), not this process's: Node running under Rosetta calls itself
|
|
@@ -86,6 +93,8 @@ export function standaloneUrl(build, pin = STANDALONE_PYTHON) {
|
|
|
86
93
|
export function hostKey({ platform = process.platform, arch = process.arch, sysctlArm64 = '' } = {}) {
|
|
87
94
|
if (platform === 'darwin') return `darwin-${chipOf({ sysctlArm64, processArch: arch })}`;
|
|
88
95
|
if (platform === 'linux' && (arch === 'x64' || arch === 'arm64')) return `linux-${arch}`;
|
|
96
|
+
// Windows has a pinned build (the desktop app bundles it, scripts/build-desktop.mjs)
|
|
97
|
+
// but no Set up the scanner yet: that path hasn't been tried there (DESKTOP.md D3).
|
|
89
98
|
return null;
|
|
90
99
|
}
|
|
91
100
|
|
|
@@ -126,6 +135,16 @@ export const SYSTEM_CANDIDATES = Object.freeze([
|
|
|
126
135
|
'python3.14', 'python3.13', 'python3.12', 'python3.11', 'python3.10',
|
|
127
136
|
]);
|
|
128
137
|
|
|
138
|
+
/**
|
|
139
|
+
* On Windows: the `py` launcher first, which finds a real install; `python` and
|
|
140
|
+
* `python3` there are often the Microsoft Store's stand-ins, which open the Store
|
|
141
|
+
* instead of running anything.
|
|
142
|
+
*/
|
|
143
|
+
export const WINDOWS_CANDIDATES = Object.freeze(['py', 'python', 'python3']);
|
|
144
|
+
|
|
145
|
+
/** The names to try on this operating system. */
|
|
146
|
+
export const systemCandidatesFor = (platform = process.platform) => (platform === 'win32' ? WINDOWS_CANDIDATES : SYSTEM_CANDIDATES);
|
|
147
|
+
|
|
129
148
|
/** Is "3.12.4" a Python the pinned packages install into? */
|
|
130
149
|
export function systemPythonFits(version, range = SYSTEM_PYTHON) {
|
|
131
150
|
const m = String(version || '').match(/^(\d+)\.(\d+)/);
|
|
@@ -187,10 +206,14 @@ export function parseProbe(stdout) {
|
|
|
187
206
|
* bundle, which breaks its signature
|
|
188
207
|
* -u unbuffered, which PYTHONUNBUFFERED said before -E: the Scan page shows
|
|
189
208
|
* the scanner's lines as they come
|
|
209
|
+
* -X utf8 UTF-8 for its output whatever the system's code page: on Windows a
|
|
210
|
+
* piped stdout is cp1252, and scrape.py's "→" would end the scan with a
|
|
211
|
+
* UnicodeEncodeError. PYTHONUTF8 can't say it: -E ignores it. Harmless
|
|
212
|
+
* on a Mac or Linux, already UTF-8 there.
|
|
190
213
|
* Not -I: that also takes scrape.py's own folder off the import path, and with
|
|
191
214
|
* it `import image_store`.
|
|
192
215
|
*/
|
|
193
|
-
export const OWN_PYTHON_FLAGS = Object.freeze(['-E', '-s', '-B', '-u']);
|
|
216
|
+
export const OWN_PYTHON_FLAGS = Object.freeze(['-E', '-s', '-B', '-u', '-X', 'utf8']);
|
|
194
217
|
|
|
195
218
|
/**
|
|
196
219
|
* The environment the app's own Python runs with, beside OWN_PYTHON_FLAGS. The
|
|
@@ -205,6 +228,7 @@ export function ownPythonEnv(env = process.env) {
|
|
|
205
228
|
}
|
|
206
229
|
out.PYTHONNOUSERSITE = '1';
|
|
207
230
|
out.PYTHONDONTWRITEBYTECODE = '1';
|
|
231
|
+
out.PYTHONUTF8 = '1'; // for what it starts (-X utf8 is the interpreter's own)
|
|
208
232
|
return out;
|
|
209
233
|
}
|
|
210
234
|
|
|
@@ -216,7 +240,8 @@ export function ownPythonEnv(env = process.env) {
|
|
|
216
240
|
* app that folder is inside the signed app, whichever Python runs the scan.
|
|
217
241
|
*/
|
|
218
242
|
export function noBytecodeEnv(env = process.env) {
|
|
219
|
-
|
|
243
|
+
// And UTF-8 output: on Windows a piped stdout is cp1252 otherwise (OWN_PYTHON_FLAGS).
|
|
244
|
+
return { ...env, PYTHONDONTWRITEBYTECODE: '1', PYTHONUTF8: '1' };
|
|
220
245
|
}
|
|
221
246
|
|
|
222
247
|
/** Is this Python the app's own (choosePython's 'bundled' or 'custom'), run isolated? */
|
|
@@ -321,8 +346,13 @@ export function venvPython(dataDir, platform = process.platform) {
|
|
|
321
346
|
|
|
322
347
|
/** Where "Set up the scanner" puts the standalone Python, and its interpreter. */
|
|
323
348
|
export const DOWNLOADED_DIR = 'python';
|
|
324
|
-
export function downloadedPython(dataDir) {
|
|
325
|
-
return path.join(dataDir, DOWNLOADED_DIR,
|
|
349
|
+
export function downloadedPython(dataDir, platform = process.platform) {
|
|
350
|
+
return standalonePython(path.join(dataDir, DOWNLOADED_DIR), platform);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** The interpreter inside an unpacked python-build-standalone folder: python.exe at its top on Windows, bin/python3 elsewhere. */
|
|
354
|
+
export function standalonePython(dir, platform = process.platform) {
|
|
355
|
+
return platform === 'win32' ? path.join(dir, 'python.exe') : path.join(dir, 'bin', 'python3');
|
|
326
356
|
}
|
|
327
357
|
|
|
328
358
|
// Private working folders for a setup, inside the data folder (lib/data-folder.js
|
|
@@ -342,7 +372,7 @@ export function runProbe(cmd, args, { timeoutMs = 8000, env } = {}) {
|
|
|
342
372
|
const finish = (value) => { if (!done) { done = true; resolve(value); } };
|
|
343
373
|
let child;
|
|
344
374
|
try {
|
|
345
|
-
child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'], ...(env ? { env } : {}) });
|
|
375
|
+
child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true, ...(env ? { env } : {}) });
|
|
346
376
|
} catch (err) {
|
|
347
377
|
finish({ ok: false, stdout, stderr, problem: err.message });
|
|
348
378
|
return;
|
|
@@ -406,7 +436,7 @@ export async function choosePython({
|
|
|
406
436
|
probe = runProbe,
|
|
407
437
|
exists = fs.existsSync,
|
|
408
438
|
host = null,
|
|
409
|
-
systemCandidates =
|
|
439
|
+
systemCandidates = systemCandidatesFor(),
|
|
410
440
|
timeouts = { own: 20000, other: 8000 },
|
|
411
441
|
env = process.env,
|
|
412
442
|
} = {}) {
|
package/lib/scraper-client.js
CHANGED
|
@@ -121,7 +121,7 @@ const QUIET_MS = 5000;
|
|
|
121
121
|
/** Before the first answer, and on the server, where nothing is asked. */
|
|
122
122
|
export const SCANNER_UNKNOWN = Object.freeze({
|
|
123
123
|
known: false, running: false, pending: false, action: null, target: null,
|
|
124
|
-
startedAt: null, progress: null, log: [], finished: [],
|
|
124
|
+
startedAt: null, progress: null, pages: 0, log: [], finished: [],
|
|
125
125
|
});
|
|
126
126
|
|
|
127
127
|
const listeners = new Set();
|
|
@@ -164,6 +164,7 @@ function take(s) {
|
|
|
164
164
|
target: running ? s.target ?? null : starting ? now.target : null,
|
|
165
165
|
startedAt: running ? s.startedAt ?? null : null,
|
|
166
166
|
progress: running ? s.progress ?? null : null,
|
|
167
|
+
pages: running ? Number(s.pages) || 0 : 0, // pages read so far (app/api/scraper/route.js)
|
|
167
168
|
budget: running ? s.budget ?? null : null,
|
|
168
169
|
log: running && Array.isArray(s.log) ? s.log : [],
|
|
169
170
|
finished,
|
package/lib/separation.js
CHANGED
|
@@ -253,8 +253,10 @@ const PILL_W_LAYOUT = 236; // SeparationView's PILL_W: the connection pills' w
|
|
|
253
253
|
* @returns {{ width, height, you, people, bridges, links, spokes }}
|
|
254
254
|
*/
|
|
255
255
|
export function summitLayout(top = [], width = 800, isMobile = false, { cards = false } = {}) {
|
|
256
|
-
|
|
257
|
-
|
|
256
|
+
// Cards are 40 px tall and pills 28: rows a few px apart, so ten fit a laptop's
|
|
257
|
+
// window with the slider above them (Blake, 2026-10-03: "too zoomed in" on his MacBook).
|
|
258
|
+
const gap = cards ? 45 : isMobile ? 36 : 26;
|
|
259
|
+
const bridgeGap = cards ? 32 : MIN_BRIDGE_GAP;
|
|
258
260
|
const youX = 20;
|
|
259
261
|
const bx = Math.round(width * 0.42);
|
|
260
262
|
const px = Math.round(width * 0.58);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// The theme in use, in this browser (lib/themes.js has what a theme is). Read
|
|
2
|
+
// once when the page loads, before anything draws, so the dots, your dot and
|
|
3
|
+
// the map's lines come out in its colours; changed from Settings → Appearance.
|
|
4
|
+
//
|
|
5
|
+
// Kept in localStorage, like the Galaxy's sliders: a look belongs to the
|
|
6
|
+
// screen it's on. The CSS variables are kept beside it, ready made, for the
|
|
7
|
+
// script in app/layout.js that puts them on the page before it paints, so a
|
|
8
|
+
// theme never flashes the standard colours first.
|
|
9
|
+
|
|
10
|
+
import { cleanTheme, resolveTheme, themeVars, themeAttrs, TIER_COLORS, MAP_LOOK } from './themes.js';
|
|
11
|
+
|
|
12
|
+
const KEY = 'six-degrees-theme';
|
|
13
|
+
export const VARS_KEY = 'six-degrees-theme-vars';
|
|
14
|
+
const DEFAULT = Object.freeze({ base: 'standard', custom: {} });
|
|
15
|
+
|
|
16
|
+
const listeners = new Set();
|
|
17
|
+
let choice = DEFAULT;
|
|
18
|
+
|
|
19
|
+
function read() {
|
|
20
|
+
try { return cleanTheme(JSON.parse(localStorage.getItem(KEY) || 'null') || DEFAULT); } catch { return DEFAULT; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The dots and the map's look, and the page's variables, for a choice. */
|
|
24
|
+
function apply(next) {
|
|
25
|
+
const theme = resolveTheme(next);
|
|
26
|
+
Object.assign(TIER_COLORS, theme.tiers);
|
|
27
|
+
Object.assign(MAP_LOOK, { you: theme.you, lines: theme.lines, line: theme.line, bg: theme.bg });
|
|
28
|
+
if (typeof document === 'undefined') return;
|
|
29
|
+
const vars = themeVars(next);
|
|
30
|
+
const root = document.documentElement;
|
|
31
|
+
for (const [k, v] of Object.entries(vars)) root.style.setProperty(k, v);
|
|
32
|
+
Object.assign(root.dataset, themeAttrs(next));
|
|
33
|
+
try { localStorage.setItem(VARS_KEY, JSON.stringify({ vars, attrs: themeAttrs(next) })); } catch { /* it's applied anyway */ }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (typeof window !== 'undefined') {
|
|
37
|
+
choice = read();
|
|
38
|
+
apply(choice);
|
|
39
|
+
// Another window changed it.
|
|
40
|
+
window.addEventListener('storage', (e) => {
|
|
41
|
+
if (e.key !== KEY) return;
|
|
42
|
+
choice = read();
|
|
43
|
+
apply(choice);
|
|
44
|
+
listeners.forEach((f) => f());
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const themeNow = () => choice;
|
|
49
|
+
export const serverTheme = () => DEFAULT;
|
|
50
|
+
|
|
51
|
+
export function setTheme(next) {
|
|
52
|
+
choice = cleanTheme(next);
|
|
53
|
+
try { localStorage.setItem(KEY, JSON.stringify(choice)); } catch { /* this visit only */ }
|
|
54
|
+
apply(choice);
|
|
55
|
+
listeners.forEach((f) => f());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function watchTheme(f) {
|
|
59
|
+
listeners.add(f);
|
|
60
|
+
return () => listeners.delete(f);
|
|
61
|
+
}
|