six-degrees 0.7.0 → 1.0.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/app-path-routes-manifest.json +1 -0
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +32 -4
- package/.next/standalone/.next/routes-manifest.json +6 -0
- 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 +16 -16
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
- 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 +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.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 +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/backup/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/csv/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/restore/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 +3 -3
- 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 +4 -4
- 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 +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 +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 +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 +18 -18
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +18 -18
- 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 +16 -16
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +19 -19
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +19 -19
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/insights/page/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/insights/page/build-manifest.json +20 -0
- package/.next/standalone/.next/server/app/insights/page/next-font-manifest.json +11 -0
- package/.next/standalone/.next/server/app/insights/page/react-loadable-manifest.json +1 -0
- package/.next/standalone/.next/server/app/insights/page/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/insights/page.js +13 -0
- package/.next/standalone/.next/server/app/insights/page.js.map +5 -0
- package/.next/standalone/.next/server/app/insights/page.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/insights/page_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/insights.html +1 -0
- package/.next/standalone/.next/server/app/insights.meta +26 -0
- package/.next/standalone/.next/server/app/insights.rsc +35 -0
- package/.next/standalone/.next/server/app/insights.segments/_full.segment.rsc +35 -0
- package/.next/standalone/.next/server/app/insights.segments/_tree.segment.rsc +4 -0
- package/.next/standalone/.next/server/app/insights.segments/insights/__PAGE__.segment.rsc +33 -0
- 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 +18 -18
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +18 -18
- 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 +16 -16
- 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 +19 -19
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +19 -19
- 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 +16 -16
- 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 +19 -19
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +19 -19
- 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 +16 -16
- 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 +19 -19
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +19 -19
- 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 +21 -20
- 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 +18 -18
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +18 -18
- 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 +16 -16
- 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 +19 -19
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +19 -19
- 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 +16 -16
- 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 +19 -19
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +19 -19
- 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 +16 -16
- 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 +18 -18
- package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +18 -18
- 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 +16 -16
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__063s0x7._.js → [root-of-the-server]__0r-0t-9._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ra6xyg._.js +15 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__20rwfdw._.js → [root-of-the-server]__0rcpjad._.js} +5 -2
- 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/lib_02iz_9q._.js +2 -2
- package/.next/standalone/.next/server/chunks/lib_05agvk3._.js +2 -2
- package/.next/standalone/.next/server/chunks/lib_0v84u66._.js +2 -2
- package/.next/standalone/.next/server/chunks/lib_db-client_0_dch35.js +9 -9
- package/.next/standalone/.next/server/chunks/{lib_rpc_0jnfp2e.js → lib_rpc_00nndai.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_0b17r6v.js → lib_rpc_03ibb-x.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_0doq0t-.js → lib_rpc_03ri3ji.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_0fd2rlw.js → lib_rpc_078ce48.js} +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0a69mgr.js +3 -0
- package/.next/standalone/.next/server/chunks/{lib_rpc_005abye.js → lib_rpc_0cej3t8.js} +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0cmh4g6.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0nx2_pc.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0r79-w1.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0yx8ynd.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_12p-o9-.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1466dnb.js +3 -0
- package/.next/standalone/.next/server/chunks/{lib_rpc_0is4qoc.js → lib_rpc_1hjjjr4.js} +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_1sryxv6.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1trn-x5.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1ygu_mv.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0egamyq._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_04am_h8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_07zb7lu._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0_79idq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0gds903._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0klqt9z._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_1k9qfsk._.js → _14jak5s._.js} +6 -2
- package/.next/standalone/.next/server/chunks/ssr/_14nxd3q._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1cj2yog._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1fm1ih_._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1hdqw0-._.js +7 -0
- package/.next/standalone/.next/server/chunks/ssr/_1k2nmbw._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0gy11ar._.js → _1r62six._.js} +6 -2
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_insights_page_actions_0-0tf75.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_components_0uuff77._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_components_1j3q-h5._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_01o3i5y._.js → app_components_settings_SectorPicker_1c-29qw.js} +2 -6
- package/.next/standalone/.next/server/chunks/ssr/app_components_ui_0pdmz2z.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_insights_page_1d_chja.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +14 -14
- package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +3 -3
- 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/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/next-font-manifest.js +1 -1
- package/.next/standalone/.next/server/next-font-manifest.json +4 -0
- 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/prefetch-hints.json +14 -0
- 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/02wnf8dkxegxg.js +34 -0
- package/.next/standalone/.next/static/chunks/0hggugbh84m4t.js +21 -0
- package/.next/{static/chunks/2eo9pqq9gay3r.js → standalone/.next/static/chunks/0ka4prpk3w9wn.js} +4 -4
- package/.next/standalone/.next/static/chunks/0s_xxwp7z88vv.js +1 -0
- package/.next/standalone/.next/static/chunks/1-nnl8amfm4yh.js +57 -0
- package/.next/{static/chunks/44yn0tcd9dfk3.js → standalone/.next/static/chunks/1bkt_hhjzvbh8.js} +1 -1
- package/.next/standalone/.next/static/chunks/1cmphb3kh2ers.css +3 -0
- package/.next/{static/chunks/1qbjermbf68xj.js → standalone/.next/static/chunks/1d8yv3k_atka7.js} +1 -1
- package/.next/standalone/.next/static/chunks/1ods5h86raivz.js +1 -0
- package/.next/standalone/.next/static/chunks/23hc51oa4vkhc.js +5 -0
- package/.next/standalone/.next/static/chunks/27n8k9wxlz5-7.js +1 -0
- package/.next/standalone/.next/static/chunks/{0p3g5tfostsep.js → 2q9i7pcxcnph1.js} +2 -2
- package/.next/{static/chunks/2zf4jmz9l3li2.js → standalone/.next/static/chunks/2w-y83mjlpnse.js} +1 -1
- package/.next/standalone/.next/static/chunks/2ws1xf293j-tm.js +5 -0
- package/.next/standalone/.next/static/chunks/3cqn-r-y5rrw1.js +5 -0
- package/.next/standalone/.next/static/chunks/3opgg1efrx8q6.js +1 -0
- package/.next/standalone/.next/static/chunks/3p51fpxxi4d5y.js +34 -0
- package/.next/{static/chunks/0an_b6zi1r_qq.js → standalone/.next/static/chunks/3y40z6of3r9py.js} +1 -5
- package/.next/standalone/.next/static/chunks/403dpflln8ah5.js +1 -0
- package/.next/standalone/CHANGELOG.md +83 -0
- package/.next/standalone/README.md +40 -14
- package/.next/standalone/SECURITY.md +17 -4
- package/.next/standalone/app/api/outreach/route.js +13 -73
- package/.next/standalone/app/api/scraper/route.js +106 -5
- package/.next/standalone/app/components/AppHeader.js +22 -6
- package/.next/standalone/app/components/AppTabs.js +4 -0
- package/.next/standalone/app/components/AutoConnect.js +286 -0
- package/.next/standalone/app/components/ChainView.js +75 -2
- package/.next/standalone/app/components/FilterPanel.js +8 -1
- package/.next/standalone/app/components/LinkedInLimits.js +38 -13
- package/.next/standalone/app/components/OutlinkQuest.js +32 -11
- package/.next/standalone/app/components/ScanStatusBar.js +18 -3
- package/.next/standalone/app/components/ScanTrail.js +13 -3
- package/.next/standalone/app/components/SeparationView.js +6 -22
- package/.next/standalone/app/components/Sidebar.js +147 -38
- package/.next/standalone/app/components/insights/Boards.js +275 -0
- package/.next/standalone/app/components/insights/PeopleView.js +266 -0
- package/.next/standalone/app/components/insights/Rail.js +156 -0
- package/.next/standalone/app/components/insights/Report.js +382 -0
- package/.next/standalone/app/components/insights/parts.js +239 -0
- package/.next/standalone/app/components/settings/UsageSection.js +50 -3
- package/.next/standalone/app/globals.css +2 -3
- package/.next/standalone/app/insights/page.js +220 -0
- package/.next/standalone/app/page.js +1 -17
- package/.next/standalone/app/queue/page.js +30 -18
- package/.next/standalone/app/setup/page.js +71 -13
- package/.next/standalone/install.sh +7 -6
- package/.next/standalone/lib/auto-connect.js +145 -0
- package/.next/standalone/lib/companies.js +1 -1
- package/.next/standalone/lib/in-progress.js +46 -0
- package/.next/standalone/lib/insights-board.js +613 -0
- package/.next/standalone/lib/linkedin-limits.js +36 -11
- package/.next/standalone/lib/requests-client.js +10 -0
- package/.next/standalone/lib/requests.js +124 -0
- package/.next/standalone/lib/scan-state.js +1 -0
- package/.next/standalone/lib/scanner-setup.js +82 -3
- package/.next/standalone/lib/scraper-client.js +18 -5
- package/.next/standalone/lib/settings.js +6 -0
- package/.next/standalone/lib/usage.js +5 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/build-app.mjs +167 -11
- package/.next/standalone/scripts/build-desktop.mjs +3 -2
- package/.next/standalone/scripts/ci-mac-signing.sh +128 -0
- package/.next/standalone/scripts/entitlements/electron.plist +23 -0
- package/.next/standalone/scripts/entitlements/node.plist +27 -0
- package/.next/standalone/scripts/mac-sign.mjs +563 -0
- package/.next/standalone/scripts/make-dmg-background.mjs +2 -1
- package/.next/standalone/scripts/scrape.py +569 -9
- package/.next/standalone/scripts/set-signing-secrets.sh +96 -0
- package/.next/static/chunks/02wnf8dkxegxg.js +34 -0
- package/.next/static/chunks/0hggugbh84m4t.js +21 -0
- package/.next/{standalone/.next/static/chunks/2eo9pqq9gay3r.js → static/chunks/0ka4prpk3w9wn.js} +4 -4
- package/.next/static/chunks/0s_xxwp7z88vv.js +1 -0
- package/.next/static/chunks/1-nnl8amfm4yh.js +57 -0
- package/.next/{standalone/.next/static/chunks/44yn0tcd9dfk3.js → static/chunks/1bkt_hhjzvbh8.js} +1 -1
- package/.next/static/chunks/1cmphb3kh2ers.css +3 -0
- package/.next/{standalone/.next/static/chunks/1qbjermbf68xj.js → static/chunks/1d8yv3k_atka7.js} +1 -1
- package/.next/static/chunks/1ods5h86raivz.js +1 -0
- package/.next/static/chunks/23hc51oa4vkhc.js +5 -0
- package/.next/static/chunks/27n8k9wxlz5-7.js +1 -0
- package/.next/static/chunks/{0p3g5tfostsep.js → 2q9i7pcxcnph1.js} +2 -2
- package/.next/{standalone/.next/static/chunks/2zf4jmz9l3li2.js → static/chunks/2w-y83mjlpnse.js} +1 -1
- package/.next/static/chunks/2ws1xf293j-tm.js +5 -0
- package/.next/static/chunks/3cqn-r-y5rrw1.js +5 -0
- package/.next/static/chunks/3opgg1efrx8q6.js +1 -0
- package/.next/static/chunks/3p51fpxxi4d5y.js +34 -0
- package/.next/{standalone/.next/static/chunks/0an_b6zi1r_qq.js → static/chunks/3y40z6of3r9py.js} +1 -5
- package/.next/static/chunks/403dpflln8ah5.js +1 -0
- package/CHANGELOG.md +83 -0
- package/README.md +40 -14
- package/SECURITY.md +17 -4
- package/lib/auto-connect.js +145 -0
- package/lib/companies.js +1 -1
- package/lib/in-progress.js +46 -0
- package/lib/insights-board.js +613 -0
- package/lib/linkedin-limits.js +36 -11
- package/lib/requests-client.js +10 -0
- package/lib/requests.js +124 -0
- package/lib/scan-state.js +1 -0
- package/lib/scanner-setup.js +82 -3
- package/lib/scraper-client.js +18 -5
- package/lib/settings.js +6 -0
- package/lib/usage.js +5 -0
- package/package.json +1 -1
- package/scripts/scrape.py +569 -9
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_4y8z7._.js +0 -8
- package/.next/standalone/.next/server/chunks/_02twlca._.js +0 -10
- package/.next/standalone/.next/server/chunks/lib_rpc_0nef_n7.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0plrzra.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_13a4m0c.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1if17c-.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1j-zw7v.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1sngt-q.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1v2sgme.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1vh-tgx.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1zaubz7.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_02sband._.js +0 -7
- package/.next/standalone/.next/server/chunks/ssr/_189g8lg._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1f_n00p._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_components_0t908t-._.js +0 -3
- package/.next/standalone/.next/static/chunks/0e3g0sil79cus.js +0 -21
- package/.next/standalone/.next/static/chunks/0zd0t7e60bn4z.js +0 -1
- package/.next/standalone/.next/static/chunks/163b-20vwa06i.js +0 -34
- package/.next/standalone/.next/static/chunks/1pa-2v1ajvrko.js +0 -1
- package/.next/standalone/.next/static/chunks/23_1z47al92nz.js +0 -57
- package/.next/standalone/.next/static/chunks/260bomtr214m8.js +0 -34
- package/.next/standalone/.next/static/chunks/2xk2ra4piiuod.js +0 -34
- package/.next/standalone/.next/static/chunks/3b0-14cxqsprt.js +0 -1
- package/.next/standalone/.next/static/chunks/3jarus1_38s2z.js +0 -1
- package/.next/standalone/.next/static/chunks/3ssu341hav_sq.css +0 -3
- package/.next/standalone/.next/static/chunks/3v1qd3sqhqn0d.js +0 -1
- package/.next/static/chunks/0e3g0sil79cus.js +0 -21
- package/.next/static/chunks/0zd0t7e60bn4z.js +0 -1
- package/.next/static/chunks/163b-20vwa06i.js +0 -34
- package/.next/static/chunks/1pa-2v1ajvrko.js +0 -1
- package/.next/static/chunks/23_1z47al92nz.js +0 -57
- package/.next/static/chunks/260bomtr214m8.js +0 -34
- package/.next/static/chunks/2xk2ra4piiuod.js +0 -34
- package/.next/static/chunks/3b0-14cxqsprt.js +0 -1
- package/.next/static/chunks/3jarus1_38s2z.js +0 -1
- package/.next/static/chunks/3ssu341hav_sq.css +0 -3
- package/.next/static/chunks/3v1qd3sqhqn0d.js +0 -1
- /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → mwCGF3OV3CWV4SjUgg5T8}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → mwCGF3OV3CWV4SjUgg5T8}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → mwCGF3OV3CWV4SjUgg5T8}/_ssgManifest.js +0 -0
- /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → mwCGF3OV3CWV4SjUgg5T8}/_buildManifest.js +0 -0
- /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → mwCGF3OV3CWV4SjUgg5T8}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → mwCGF3OV3CWV4SjUgg5T8}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// Insights → Report (mock-up C): the same numbers as the boards, as a page you
|
|
4
|
+
// scroll, one section each: a big number, one line that says what it means,
|
|
5
|
+
// a picture of it, and where it came from. Nothing here is computed apart
|
|
6
|
+
// from lib/insights-board.js.
|
|
7
|
+
|
|
8
|
+
import Link from 'next/link';
|
|
9
|
+
import Avatar from '../Avatar';
|
|
10
|
+
import { TIER_COLORS } from '../../../lib/themes';
|
|
11
|
+
import { shortName } from '../../../lib/separation';
|
|
12
|
+
import { RARITY } from '../../../lib/rarity';
|
|
13
|
+
import { DECIDES } from '../../../lib/insights-board';
|
|
14
|
+
import { fmt, one, pc, TierChip, Bars, NeedsCircles, LINE, CARD_BG, ELLIPSIS } from './parts';
|
|
15
|
+
|
|
16
|
+
const TIERS = ['S', 'A', 'B', 'C', 'D'];
|
|
17
|
+
const today = () => new Date().toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' });
|
|
18
|
+
|
|
19
|
+
function Section({ id, no, big, bigStyle, head, lede, children, src, isMobile, last }) {
|
|
20
|
+
return (
|
|
21
|
+
<section id={id} style={{ padding: isMobile ? '34px 0 30px' : '44px 0 40px', borderBottom: last ? 'none' : `1px solid ${LINE}`, scrollMarginTop: 80 }}>
|
|
22
|
+
<div style={{ fontSize: 12, fontWeight: 800, color: 'var(--sd-fg-4, #6f7a88)', letterSpacing: 1.5, marginBottom: 8 }}>{String(no).padStart(2, '0')}</div>
|
|
23
|
+
<div style={{ fontSize: isMobile ? 56 : 84, lineHeight: 0.95, fontWeight: 850, letterSpacing: isMobile ? -2 : -3, fontVariantNumeric: 'tabular-nums', overflowWrap: 'anywhere', ...bigStyle }}>{big}</div>
|
|
24
|
+
<h2 style={{ fontSize: isMobile ? 21 : 26, lineHeight: 1.2, margin: '12px 0 8px', fontWeight: 750, letterSpacing: -0.4, maxWidth: 720, color: 'var(--sd-fg-1, #fff)' }}>{head}</h2>
|
|
25
|
+
{lede && <p style={{ fontSize: 15, color: 'var(--sd-fg-2, #b8c4c4)', lineHeight: 1.55, margin: '0 0 20px', maxWidth: 700 }}>{lede}</p>}
|
|
26
|
+
{children}
|
|
27
|
+
{src && <div style={{ fontSize: 11.5, color: 'var(--sd-fg-4, #6f7a88)', marginTop: 16, lineHeight: 1.5, maxWidth: 760 }}>{src}</div>}
|
|
28
|
+
</section>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const W = ({ children }) => <b style={{ color: 'var(--sd-fg-1, #fff)' }}>{children}</b>;
|
|
33
|
+
const Key = ({ color, children }) => (
|
|
34
|
+
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, whiteSpace: 'nowrap' }}>
|
|
35
|
+
<i style={{ width: 9, height: 9, borderRadius: 3, background: color, display: 'inline-block' }} />{children}
|
|
36
|
+
</span>
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
/** A row of a bar chart: label, bar, number. */
|
|
40
|
+
function BarRow({ label, parts, max, value, cols = '170px 1fr 64px', bold, isMobile }) {
|
|
41
|
+
return (
|
|
42
|
+
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '118px 1fr 54px' : cols, gap: 12, alignItems: 'center', margin: '7px 0' }}>
|
|
43
|
+
<span style={{ fontSize: 13, color: bold ? 'var(--sd-fg-1, #fff)' : 'var(--sd-fg-2, #b8c4c4)', fontWeight: bold ? 650 : 400, display: 'flex', alignItems: 'center', gap: 8, minWidth: 0, ...ELLIPSIS }}>{label}</span>
|
|
44
|
+
<Bars parts={parts} max={max} />
|
|
45
|
+
<b style={{ fontSize: 13.5, fontWeight: 750, textAlign: 'right', fontVariantNumeric: 'tabular-nums' }}>{value}</b>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const tierParts = (counts) => TIERS.map((t) => ({ value: counts[t] || 0, color: TIER_COLORS[t], ink: TIER_COLORS[t], label: t, title: `${fmt(counts[t])} ${t}` }));
|
|
51
|
+
|
|
52
|
+
export default function Report({ data, heavy, open, net, source, isMobile }) {
|
|
53
|
+
const { reach, kings, gates, hidden, grid, cover, sure } = data;
|
|
54
|
+
const circles = kings.scanned > 0;
|
|
55
|
+
const own = source === 'own';
|
|
56
|
+
const S = reach.tiers.d1.S + reach.tiers.d2.S;
|
|
57
|
+
const A = reach.tiers.d1.A + reach.tiers.d2.A;
|
|
58
|
+
const k0 = kings.list[0];
|
|
59
|
+
const g0 = gates.list[0];
|
|
60
|
+
const toc = [
|
|
61
|
+
['reach', 'Reach'], ['power', 'Power'], ['titles', 'Titles'],
|
|
62
|
+
...(circles ? [['kingmakers', 'Kingmakers'], ['gatekeepers', 'Gatekeepers'], ['hidden', 'Hidden giants'], ['untapped', 'Untapped']] : [['kingmakers', 'Circles']]),
|
|
63
|
+
['companies', 'Company power'], ['richest', 'Richest?'], ['sure', 'How sure'], ['left', 'What’s left'],
|
|
64
|
+
];
|
|
65
|
+
const sec = { isMobile };
|
|
66
|
+
// Sections are numbered as they're shown: with no circles, one section stands in for the four that read them.
|
|
67
|
+
let count = 0;
|
|
68
|
+
const no = () => ++count;
|
|
69
|
+
return (
|
|
70
|
+
<div>
|
|
71
|
+
{/* ── Cover ── */}
|
|
72
|
+
<div style={{ padding: isMobile ? '24px 0 24px' : '30px 0 30px', borderBottom: `1px solid ${LINE}` }}>
|
|
73
|
+
<div style={{ fontSize: 12, letterSpacing: 1.6, textTransform: 'uppercase', color: 'var(--sd-fg-3, #8b9a9a)', fontWeight: 700 }}>Network report · {today()}</div>
|
|
74
|
+
<h1 style={{ fontSize: isMobile ? 40 : 58, lineHeight: 1.02, margin: '12px 0', fontWeight: 850, letterSpacing: isMobile ? -1 : -1.5, color: 'var(--sd-fg-1, #fff)' }}>
|
|
75
|
+
Your network,<br />
|
|
76
|
+
<em style={{ fontStyle: 'normal', background: 'linear-gradient(135deg, var(--sd-gold, #FFD700), var(--sd-orange, #FF6B35) 55%, var(--sd-purple, #9B59B6))', WebkitBackgroundClip: 'text', backgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>by power.</em>
|
|
77
|
+
</h1>
|
|
78
|
+
<p style={{ fontSize: 16, color: 'var(--sd-fg-2, #b8c4c4)', margin: 0, lineHeight: 1.5, maxWidth: 620 }}>
|
|
79
|
+
{source === 'csv'
|
|
80
|
+
? <>What your {fmt(reach.d1)} connections add up to: where the power is, and what a scan of their circles would open.</>
|
|
81
|
+
: <>What your {fmt(reach.d1)} connections and the {fmt(reach.circles)} circle{reach.circles === 1 ? '' : 's'} you’ve scanned add up to: where the power is, who holds the doors, and what’s still closed.</>}
|
|
82
|
+
</p>
|
|
83
|
+
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr 1fr' : 'repeat(4, 1fr)', gap: 12, marginTop: 28 }}>
|
|
84
|
+
<CoverStat n={reach.total} label={circles ? 'people within two steps' : 'connections'} />
|
|
85
|
+
<CoverStat n={S} label="S-tier in reach" color={TIER_COLORS.S} />
|
|
86
|
+
{circles
|
|
87
|
+
? <CoverStat n={open?.total} label="S and A not asked" color="var(--sd-orange, #FF6B35)" />
|
|
88
|
+
: <CoverStat n={heavy ? heavy.companies.count : null} label="companies they work at" />}
|
|
89
|
+
<CoverStat n={reach.circles} label={`circle${reach.circles === 1 ? '' : 's'} scanned of ${fmt(reach.d1)}`} />
|
|
90
|
+
</div>
|
|
91
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 24 }}>
|
|
92
|
+
{toc.map(([id, label]) => (
|
|
93
|
+
<a key={id} href={`#${id}`} style={{ fontSize: 12, color: 'var(--sd-fg-2, #b8c4c4)', padding: '5px 10px', borderRadius: 16, border: `1px solid ${LINE}`, background: CARD_BG, textDecoration: 'none' }}>{label}</a>
|
|
94
|
+
))}
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{/* ── 01 Reach ── */}
|
|
99
|
+
<Section id="reach" no={no()} {...sec} big={fmt(reach.total)} head={circles ? 'people within two steps of you.' : 'connections, and no circles yet.'}
|
|
100
|
+
lede={circles
|
|
101
|
+
? <>Your {fmt(reach.d1)} connections, and everyone in the {fmt(reach.circles)} circle{reach.circles === 1 ? '' : 's'} you’ve scanned so far. Each circle added about <W>{fmt(cover.perCircle)}</W> people you didn’t already reach.</>
|
|
102
|
+
: source === 'csv'
|
|
103
|
+
? 'A CSV import is your connections only: LinkedIn’s export has no circles. A scan of their circles adds the people one step further out.'
|
|
104
|
+
: <>Your {fmt(reach.d1)} connections. Their circles add the people one step further out, once they’re scanned.</>}
|
|
105
|
+
src={`${reach.source}${reach.sharedRows ? ` ${fmt(reach.sharedRows)} times, someone turned up in another circle as well; they count once here.` : ''}`}>
|
|
106
|
+
<div style={{ display: 'flex', gap: 2, height: 22, borderRadius: 6, overflow: 'hidden' }}>
|
|
107
|
+
<span style={{ width: `${(100 * reach.d1) / Math.max(1, reach.total)}%`, background: 'var(--sd-green, #00ff88)' }} />
|
|
108
|
+
<span style={{ width: `${(100 * reach.d2) / Math.max(1, reach.total)}%`, background: 'var(--sd-orange, #FF6B35)' }} />
|
|
109
|
+
</div>
|
|
110
|
+
<div style={{ display: 'flex', gap: 22, marginTop: 10, fontSize: 13, color: 'var(--sd-fg-2, #b8c4c4)', flexWrap: 'wrap' }}>
|
|
111
|
+
<Key color="var(--sd-green, #00ff88)"><W>{fmt(reach.d1)}</W> your connections</Key>
|
|
112
|
+
{circles && <Key color="var(--sd-orange, #FF6B35)"><W>{fmt(reach.d2)}</W> in the circles you’ve scanned</Key>}
|
|
113
|
+
{reach.d3 > 0 && <Key color="var(--sd-fg-5, #556)"><W>{fmt(reach.d3)}</W> more from company scans, not counted here</Key>}
|
|
114
|
+
</div>
|
|
115
|
+
</Section>
|
|
116
|
+
|
|
117
|
+
{/* ── 02 Power ── */}
|
|
118
|
+
<Section id="power" no={no()} {...sec} big={fmt(S)} bigStyle={{ color: TIER_COLORS.S }}
|
|
119
|
+
head={reach.tiers.d2.S > reach.tiers.d1.S ? 'of them are S-tier, and most sit one step out.' : 'of them are S-tier.'}
|
|
120
|
+
lede={circles
|
|
121
|
+
? <><W>{fmt(reach.tiers.d2.S)}</W> S-tier people are in your 2nd degree against <W>{fmt(reach.tiers.d1.S)}</W> among your own connections. Another {fmt(A)} are A.</>
|
|
122
|
+
: <>That’s {pc((100 * reach.tiers.d1.S) / Math.max(1, reach.d1))} of your {fmt(reach.d1)} connections. Another {fmt(A)} are A.</>}
|
|
123
|
+
src="Tiers as Scores → Tiers sets them: on your network’s curve unless you chose the fixed scale. A tier is a statement about network position, not human worth.">
|
|
124
|
+
<BarRow {...sec} label="Your connections" parts={tierParts(reach.tiers.d1)} value={`${fmt(reach.tiers.d1.S)} S`} cols="150px 1fr 80px" />
|
|
125
|
+
{circles && <BarRow {...sec} label="Your 2nd degree" parts={tierParts(reach.tiers.d2)} value={`${fmt(reach.tiers.d2.S)} S`} cols="150px 1fr 80px" />}
|
|
126
|
+
</Section>
|
|
127
|
+
|
|
128
|
+
{/* ── 03 Titles ── */}
|
|
129
|
+
<Section id="titles" no={no()} {...sec} big={heavy ? fmt(heavy.ladder.decide) : '…'}
|
|
130
|
+
head={heavy && heavy.ladder.rungs.find((r) => r.key === 'owner').total ? 'run something: C-suite, founders, owners and VPs.' : 'run something: C-suite, founders and VPs.'}
|
|
131
|
+
lede={heavy ? (() => {
|
|
132
|
+
const r = Object.fromEntries(heavy.ladder.rungs.map((x) => [x.key, x]));
|
|
133
|
+
return <>{fmt(r.csuite.total)} C-suite and founders, {fmt(r.vp.total)} VPs, partners and GMs{r.owner.total ? `, ${fmt(r.owner.total)} owners` : ''}. {fmt(heavy.ladder.decideYours)} of them are already your connections.</>;
|
|
134
|
+
})() : 'Reading every headline…'}
|
|
135
|
+
src={heavy?.ladder.source}>
|
|
136
|
+
{heavy && (() => {
|
|
137
|
+
const rungs = heavy.ladder.rungs.filter((r) => r.total);
|
|
138
|
+
const max = Math.max(1, ...rungs.map((r) => r.total));
|
|
139
|
+
return rungs.map((r) => {
|
|
140
|
+
const top = DECIDES.includes(r.key);
|
|
141
|
+
return <BarRow key={r.key} {...sec} label={r.label} bold={top} max={max} value={fmt(r.total)}
|
|
142
|
+
parts={[{ value: r.total, color: top ? 'var(--sd-fg-1, #e8ecf2)' : 'rgba(var(--sd-ink, 255, 255, 255), 0.4)' }]} />;
|
|
143
|
+
});
|
|
144
|
+
})()}
|
|
145
|
+
</Section>
|
|
146
|
+
|
|
147
|
+
{/* ── Kingmakers, Gatekeepers, Hidden giants and Untapped read circles; with none, one section says so ── */}
|
|
148
|
+
{!circles ? (
|
|
149
|
+
<Section id="kingmakers" no={no()} {...sec} big="No circles" bigStyle={{ fontSize: isMobile ? 40 : 56, letterSpacing: -1.5, color: 'var(--sd-fg-3, #8b9a9a)' }}
|
|
150
|
+
head="Kingmakers, Gatekeepers, Hidden giants and Untapped read your connections’ circles.">
|
|
151
|
+
<NeedsCircles source={source} />
|
|
152
|
+
</Section>
|
|
153
|
+
) : (<>
|
|
154
|
+
{/* ── Kingmakers ── */}
|
|
155
|
+
<Section id="kingmakers" no={no()} {...sec} big={fmt(k0.SA)} bigStyle={{ color: TIER_COLORS.S }}
|
|
156
|
+
head={<>S and A people sit behind {k0.row.name}. Your biggest door is {k0.row.tier}-tier.</>}
|
|
157
|
+
lede={<>{k0.row.name}’s circle holds {fmt(k0.S)} S and {fmt(k0.A)} A, of {fmt(k0.size)} people. A title says what someone holds; a circle says what they can open.</>}
|
|
158
|
+
src={`${kings.source} A strong circle adds up to 2 to someone’s own score.`}>
|
|
159
|
+
<>
|
|
160
|
+
{kings.list.slice(0, 6).map((k) => (
|
|
161
|
+
<BarRow key={k.row.id} {...sec} cols="230px 1fr 50px" max={k0.SA} value={fmt(k.SA)}
|
|
162
|
+
label={<><Avatar person={k.row} size={22} tierColors={TIER_COLORS} /><span style={ELLIPSIS}>{k.row.name}</span><TierChip tier={k.row.tier} size={16} /></>}
|
|
163
|
+
parts={[{ value: k.S, color: TIER_COLORS.S, ink: TIER_COLORS.S, label: 'S' }, { value: k.A, color: TIER_COLORS.A, ink: TIER_COLORS.A, label: 'A' }]} />
|
|
164
|
+
))}
|
|
165
|
+
<div style={{ display: 'flex', gap: 16, fontSize: 12, color: 'var(--sd-fg-3, #8b9a9a)', marginTop: 10 }}>
|
|
166
|
+
<Key color={TIER_COLORS.S}>S behind them</Key><Key color={TIER_COLORS.A}>A behind them</Key>
|
|
167
|
+
</div>
|
|
168
|
+
</>
|
|
169
|
+
</Section>
|
|
170
|
+
|
|
171
|
+
{/* ── Gatekeepers ── */}
|
|
172
|
+
<Section id="gatekeepers" no={no()} {...sec} big={pc(gates.onlyShare)} bigStyle={{ color: 'var(--sd-green, #00ff88)' }}
|
|
173
|
+
head="of the S and A people you can reach have one way in."
|
|
174
|
+
lede={g0 ? <>Your reach hangs on a few people. Three connections carry <W>{pc(gates.top3Share)}</W> of it, and if {g0.row.name} disappeared from LinkedIn tomorrow, <W>{fmt(g0.onlySA)}</W> S and A people would drop off your map.</> : null}
|
|
175
|
+
src={gates.source}>
|
|
176
|
+
<>
|
|
177
|
+
<div style={{ display: 'flex', gap: 2, height: 34, borderRadius: 8, overflow: 'hidden' }}>
|
|
178
|
+
{gates.top3.map((g, i) => (
|
|
179
|
+
<span key={g.row.id} style={{ width: `${g.share}%`, background: ['#00ff88', '#00c46a', '#00984f'][i], display: 'flex', alignItems: 'center', paddingLeft: 10, overflow: 'hidden' }}>
|
|
180
|
+
<em style={{ fontStyle: 'normal', fontSize: 12, fontWeight: 750, color: '#00170b', whiteSpace: 'nowrap' }}>{shortName(g.row.name)}</em>
|
|
181
|
+
</span>
|
|
182
|
+
))}
|
|
183
|
+
<span style={{ flex: 1, background: 'rgba(var(--sd-ink, 255, 255, 255), 0.1)', display: 'flex', alignItems: 'center', paddingLeft: 10 }}>
|
|
184
|
+
<em style={{ fontStyle: 'normal', fontSize: 12, color: 'var(--sd-fg-2, #b8c4c4)', whiteSpace: 'nowrap' }}>{fmt(Math.max(0, gates.list.length - 3))} others</em>
|
|
185
|
+
</span>
|
|
186
|
+
</div>
|
|
187
|
+
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(3, 1fr)', gap: 10, marginTop: 14 }}>
|
|
188
|
+
{gates.list.slice(0, 3).map((g) => (
|
|
189
|
+
<div key={g.row.id} style={{ display: 'flex', gap: 10, alignItems: 'center', padding: '10px 12px', border: `1px solid ${LINE}`, borderRadius: 10, background: CARD_BG, fontSize: 12.5, color: 'var(--sd-fg-2, #b8c4c4)', lineHeight: 1.35 }}>
|
|
190
|
+
<Avatar person={g.row} size={30} tierColors={TIER_COLORS} />
|
|
191
|
+
<span><b style={{ color: 'var(--sd-green, #00ff88)', fontSize: 16 }}>{fmt(g.onlySA)}</b> S and A only through <W>{g.row.name}</W></span>
|
|
192
|
+
</div>
|
|
193
|
+
))}
|
|
194
|
+
</div>
|
|
195
|
+
</>
|
|
196
|
+
</Section>
|
|
197
|
+
|
|
198
|
+
{/* ── Hidden giants ── */}
|
|
199
|
+
<Section id="hidden" no={no()} {...sec} big={fmt(hidden.count)} bigStyle={{ color: 'var(--sd-cyan, #00E5FF)' }}
|
|
200
|
+
head="hidden giants: S-tier, one way in." lede="The connection who knows them is the only door you have."
|
|
201
|
+
src={hidden.source}>
|
|
202
|
+
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: 10 }}>
|
|
203
|
+
{hidden.list.slice(0, 6).map((h) => (
|
|
204
|
+
<div key={h.key} style={{ display: 'flex', gap: 12, alignItems: 'center', padding: '12px 14px', border: `1px solid ${LINE}`, borderRadius: 12, background: CARD_BG, minWidth: 0 }}>
|
|
205
|
+
<Avatar person={h.row} size={36} tierColors={TIER_COLORS} />
|
|
206
|
+
<div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 2 }}>
|
|
207
|
+
<b style={{ fontSize: 14, ...ELLIPSIS }}>{h.row.name}</b>
|
|
208
|
+
<span style={{ fontSize: 12, color: 'var(--sd-fg-3, #8b9a9a)', ...ELLIPSIS }}>{h.row.company || h.row.headline}</span>
|
|
209
|
+
<span style={{ fontSize: 11.5, color: 'var(--sd-fg-2, #b8c4c4)', ...ELLIPSIS }}>
|
|
210
|
+
only via {h.via ? h.via.name : 'a connection the app can’t name'} · {h.from === 'linkedin' ? '1 mutual connection (LinkedIn)' : '1 way in (your scans)'}
|
|
211
|
+
</span>
|
|
212
|
+
</div>
|
|
213
|
+
<em style={{ fontStyle: 'normal', fontWeight: 800, fontSize: 17, color: TIER_COLORS.S }}>{one(h.score)}</em>
|
|
214
|
+
</div>
|
|
215
|
+
))}
|
|
216
|
+
</div>
|
|
217
|
+
</Section>
|
|
218
|
+
|
|
219
|
+
{/* ── Untapped ── */}
|
|
220
|
+
<Section id="untapped" no={no()} {...sec} big={fmt(open?.total)} bigStyle={{ color: 'var(--sd-orange, #FF6B35)' }}
|
|
221
|
+
head={open?.askedSA === 0 ? 'S and A people you haven’t asked. That’s all of them.' : 'S and A people you haven’t asked.'}
|
|
222
|
+
lede={<><W>{fmt(grid.grid.S.warm)}</W> of the S-tier are warm, with 31 or more mutual connections: the easiest asks you have. <W>{fmt(grid.grid.S.only)}</W> are the hidden giants above.</>}
|
|
223
|
+
src={`${open?.source} ${grid.source}`}>
|
|
224
|
+
<RarityGrid grid={grid.grid} isMobile={isMobile} />
|
|
225
|
+
</Section>
|
|
226
|
+
</>)}
|
|
227
|
+
|
|
228
|
+
{/* ── 08 Company power ── */}
|
|
229
|
+
<Section id="companies" no={no()} {...sec} big={heavy?.companies.list[0]?.name || '…'} bigStyle={{ fontSize: isMobile ? 38 : 56, letterSpacing: -1.5, color: 'var(--sd-fg-1, #fff)' }}
|
|
230
|
+
head="holds the most power you can reach."
|
|
231
|
+
lede={heavy?.companies.list[0] ? (() => {
|
|
232
|
+
const c = heavy.companies.list[0];
|
|
233
|
+
const ind = heavy.industries.list[0];
|
|
234
|
+
return <>{fmt(c.people)} people work there, {fmt(c.S)} of them S-tier, with <W>{fmt(c.waysIn)}</W> way{c.waysIn === 1 ? '' : 's'} in.{ind ? <> By industry, <W>{ind.label}</W> holds {pc(ind.share)} of your S and A.</> : null}</>;
|
|
235
|
+
})() : null}
|
|
236
|
+
src={heavy ? `${heavy.companies.source} ${heavy.industries.source}` : null}>
|
|
237
|
+
{heavy && (() => {
|
|
238
|
+
const cos = heavy.companies.list.slice(0, 5);
|
|
239
|
+
const max = cos[0]?.total || 1;
|
|
240
|
+
return (
|
|
241
|
+
<>
|
|
242
|
+
{cos.map((c) => (
|
|
243
|
+
<BarRow key={c.name} {...sec} cols="190px 1fr 64px" max={max} value={fmt(Math.round(c.total))} label={c.name}
|
|
244
|
+
parts={[{ value: c.total, color: 'var(--sd-gold, #FFD700)', title: `${fmt(c.S)} S, ${fmt(c.people)} people` }]} />
|
|
245
|
+
))}
|
|
246
|
+
<div style={{ display: 'flex', gap: 18, flexWrap: 'wrap', marginTop: 14, fontSize: 12.5, color: 'var(--sd-fg-2, #b8c4c4)' }}>
|
|
247
|
+
{heavy.industries.list.slice(0, 4).map((e) => <Key key={e.key} color={e.color}>{e.label} <W>{pc(e.share)}</W></Key>)}
|
|
248
|
+
</div>
|
|
249
|
+
</>
|
|
250
|
+
);
|
|
251
|
+
})()}
|
|
252
|
+
</Section>
|
|
253
|
+
|
|
254
|
+
{/* ── 09 Richest? ── */}
|
|
255
|
+
<Section id="richest" no={no()} {...sec} big="Richest?" bigStyle={{ fontSize: isMobile ? 40 : 56, letterSpacing: -1.5, color: 'var(--sd-gold, #FFD700)' }}
|
|
256
|
+
head="The app can’t know, and won’t guess."
|
|
257
|
+
lede="Six Degrees doesn’t know anyone’s money. LinkedIn shows titles and companies, not wealth. The closest honest measure is power: who runs the biggest companies you can reach."
|
|
258
|
+
src={heavy?.companies.bigSource}>
|
|
259
|
+
<ul style={{ listStyle: 'none', padding: 0, margin: '0 0 18px', display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
|
260
|
+
{['Net worth', 'Pay', 'Company revenue', 'Funding raised'].map((x) => (
|
|
261
|
+
<li key={x} style={{ fontSize: 12.5, color: 'var(--sd-fg-3, #8b9a9a)', padding: '5px 10px', borderRadius: 16, border: '1px solid rgba(255,80,80,0.3)', background: 'rgba(255,80,80,0.05)', textDecoration: 'line-through' }}>{x}</li>
|
|
262
|
+
))}
|
|
263
|
+
</ul>
|
|
264
|
+
{heavy && (
|
|
265
|
+
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(3, 1fr)', gap: 10 }}>
|
|
266
|
+
<Dashed n={heavy.companies.big.people} text="people whose score rests on a company scored 8 or more, Fortune 500 scale" />
|
|
267
|
+
<Dashed n={heavy.ladder.rungs.find((r) => r.key === 'csuite').total} text="C-suite and founders within two steps, by their headline" />
|
|
268
|
+
<Dashed n={data.index.people.filter((p) => p.score >= 8).length} text="people at 8 power or more" />
|
|
269
|
+
</div>
|
|
270
|
+
)}
|
|
271
|
+
</Section>
|
|
272
|
+
|
|
273
|
+
{/* ── 10 How sure ── */}
|
|
274
|
+
<Section id="sure" no={no()} {...sec} big={pc(sure.share)} bigStyle={{ color: sure.share >= 50 ? 'var(--sd-orange, #FF6B35)' : 'var(--sd-fg-1, #fff)' }}
|
|
275
|
+
head="of these scores lean on an estimate."
|
|
276
|
+
lede={heavy ? <>{fmt(heavy.companies.estimated)} of {fmt(heavy.companies.count)} companies here are ones no list knows and you haven’t scored, so they score from how many of your people work there.{sure.tie ? <> {sure.share >= 50 ? 'That’s why' : 'At the top,'} <W>{fmt(sure.tie.count)}</W> people tie at {one(sure.tie.score)}.</> : null} Score the companies you know on the Scores tab and the top of every list separates.</> : null}
|
|
277
|
+
src={sure.source}>
|
|
278
|
+
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(3, 1fr)', gap: 10 }}>
|
|
279
|
+
<Plain n={heavy ? heavy.companies.estimated : null} text={`compan${heavy?.companies.estimated === 1 ? 'y' : 'ies'} estimated`} />
|
|
280
|
+
<Plain n={sure.title + sure.both} text="titles the app couldn’t read" />
|
|
281
|
+
<Plain n={sure.tie?.count ?? 0} text={sure.tie ? `people tied at ${one(sure.tie.score)}, the top tie` : 'no big tie at the top'} />
|
|
282
|
+
</div>
|
|
283
|
+
</Section>
|
|
284
|
+
|
|
285
|
+
{/* ── 11 What's left ── */}
|
|
286
|
+
<Section id="left" no={no()} {...sec} last big={fmt(cover.todoS)} bigStyle={{ color: TIER_COLORS.S }}
|
|
287
|
+
head={`S-tier circle${cover.todoS === 1 ? ' is' : 's are'} still closed.`}
|
|
288
|
+
lede={source === 'csv'
|
|
289
|
+
? 'A CSV import is your connections only. Scanning your own network opens their circles.'
|
|
290
|
+
: <>You’ve scanned {fmt(cover.scanned)} of {fmt(cover.total)} circles, {fmt(cover.byTier.S.scanned)} of them S-tier{cover.hidden ? `, and ${fmt(cover.hidden)} keep their list hidden` : ''}. {cover.strongest.length ? <>The next {cover.strongest.length === 1 ? 'strongest is' : `${cover.strongest.length} strongest are`} below: up to {fmt(cover.nextSearches)} searches, about {fmt(cover.nextDays)} day{cover.nextDays === 1 ? '' : 's'} at {fmt(cover.daily)} a day.</> : null}</>}
|
|
291
|
+
src={source === 'csv' ? null : <>{cover.source} Lists read to the end: {fmt(cover.lists.full)}; stopped partway: {fmt(cover.lists.partial)}; not recorded: {fmt(cover.lists.unknown)}.</>}>
|
|
292
|
+
{TIERS.map((t) => {
|
|
293
|
+
const c = cover.byTier[t];
|
|
294
|
+
return (
|
|
295
|
+
<div key={t} style={{ display: 'grid', gridTemplateColumns: '30px 1fr 90px', gap: 12, alignItems: 'center', margin: '5px 0' }}>
|
|
296
|
+
<TierChip tier={t} />
|
|
297
|
+
<Bars max={c.total || 1} height={12} parts={[{ value: c.scanned, color: TIER_COLORS[t] }, { value: c.hidden, color: 'rgba(var(--sd-ink, 255, 255, 255), 0.25)' }]} />
|
|
298
|
+
<span style={{ fontSize: 12.5, color: 'var(--sd-fg-3, #8b9a9a)', textAlign: 'right', fontVariantNumeric: 'tabular-nums' }}><W>{fmt(c.scanned)}</W> of {fmt(c.total)}</span>
|
|
299
|
+
</div>
|
|
300
|
+
);
|
|
301
|
+
})}
|
|
302
|
+
{cover.strongest.length > 0 && source !== 'csv' && (
|
|
303
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginTop: 16 }}>
|
|
304
|
+
{cover.strongest.map((p) => {
|
|
305
|
+
const chip = <><Avatar person={p} size={22} tierColors={TIER_COLORS} />{p.name}<span style={{ color: 'var(--sd-fg-4, #6f7a88)' }}>{one(p.power_score)}</span></>;
|
|
306
|
+
const style = { display: 'inline-flex', gap: 7, alignItems: 'center', fontSize: 12.5, padding: '5px 11px 5px 5px', borderRadius: 18, border: `1px solid ${LINE}`, background: CARD_BG, color: 'var(--sd-fg-1, #fff)', textDecoration: 'none' };
|
|
307
|
+
return own
|
|
308
|
+
? <Link key={p.id} href={`/setup?scan=${encodeURIComponent(p.id)}`} title={`Scan ${p.name}’s circle: opens the Scan page to confirm`} style={style}>{chip}</Link>
|
|
309
|
+
: <span key={p.id} style={style}>{chip}</span>;
|
|
310
|
+
})}
|
|
311
|
+
</div>
|
|
312
|
+
)}
|
|
313
|
+
{own && (
|
|
314
|
+
<Link href="/setup" style={{ display: 'inline-flex', fontSize: 13, fontWeight: 750, color: '#000', background: 'linear-gradient(135deg, #00ff88, #1abc9c)', padding: '8px 14px', borderRadius: 8, marginTop: 16, textDecoration: 'none' }}>
|
|
315
|
+
Open Scan →
|
|
316
|
+
</Link>
|
|
317
|
+
)}
|
|
318
|
+
</Section>
|
|
319
|
+
</div>
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function CoverStat({ n, label, color }) {
|
|
324
|
+
return (
|
|
325
|
+
<div style={{ borderLeft: `2px solid ${LINE}`, padding: '2px 0 2px 14px' }}>
|
|
326
|
+
<b style={{ display: 'block', fontSize: 30, fontWeight: 800, color: color || 'var(--sd-fg-1, #fff)', fontVariantNumeric: 'tabular-nums' }}>{n == null ? 'None' : fmt(n)}</b>
|
|
327
|
+
<span style={{ fontSize: 12.5, color: 'var(--sd-fg-3, #8b9a9a)' }}>{label}</span>
|
|
328
|
+
</div>
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function Dashed({ n, text }) {
|
|
333
|
+
return (
|
|
334
|
+
<div style={{ padding: 14, border: '1px dashed rgba(255,215,0,0.35)', borderRadius: 12, background: 'rgba(255,215,0,0.035)' }}>
|
|
335
|
+
<b style={{ fontSize: 28, fontWeight: 800, display: 'block', fontVariantNumeric: 'tabular-nums' }}>{fmt(n)}</b>
|
|
336
|
+
<span style={{ fontSize: 12.5, color: 'var(--sd-fg-2, #b8c4c4)', lineHeight: 1.4, display: 'block', marginTop: 2 }}>{text}</span>
|
|
337
|
+
</div>
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function Plain({ n, text }) {
|
|
342
|
+
return (
|
|
343
|
+
<div style={{ padding: 14, border: `1px solid ${LINE}`, borderRadius: 12, background: CARD_BG }}>
|
|
344
|
+
<b style={{ fontSize: 26, fontWeight: 800, display: 'block', fontVariantNumeric: 'tabular-nums' }}>{n == null ? '…' : fmt(n)}</b>
|
|
345
|
+
<span style={{ fontSize: 12.5, color: 'var(--sd-fg-2, #b8c4c4)', lineHeight: 1.4 }}>{text}</span>
|
|
346
|
+
</div>
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** S and A by rarity: the cells shaded by how many, the hidden giants and the warm S marked. */
|
|
351
|
+
function RarityGrid({ grid, isMobile }) {
|
|
352
|
+
const max = Math.max(1, ...['S', 'A'].flatMap((t) => RARITY.map((r) => grid[t][r.key])));
|
|
353
|
+
return (
|
|
354
|
+
<div style={{ display: 'grid', gridTemplateColumns: `34px repeat(5, minmax(0, 1fr))`, gap: 4 }}>
|
|
355
|
+
<div />
|
|
356
|
+
{RARITY.map((r) => (
|
|
357
|
+
<div key={r.key} style={{ fontSize: isMobile ? 10 : 11.5, color: 'var(--sd-fg-3, #8b9a9a)', textAlign: 'center', paddingBottom: 4, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5 }}>
|
|
358
|
+
<i className="sd-dot-html" style={{ width: 8, height: 8, borderRadius: '50%', background: r.color, flexShrink: 0 }} />{isMobile ? r.range : r.label}
|
|
359
|
+
</div>
|
|
360
|
+
))}
|
|
361
|
+
{['S', 'A'].map((t) => (
|
|
362
|
+
<div key={t} style={{ display: 'contents' }}>
|
|
363
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}><TierChip tier={t} /></div>
|
|
364
|
+
{RARITY.map((r) => {
|
|
365
|
+
const v = grid[t][r.key];
|
|
366
|
+
const hot = t === 'S' && r.key === 'only';
|
|
367
|
+
const warm = t === 'S' && r.key === 'warm';
|
|
368
|
+
return (
|
|
369
|
+
<div key={r.key} title={`${fmt(v)} ${t}-tier, ${r.label.toLowerCase()} (${r.range} mutual)`} style={{
|
|
370
|
+
height: 44, borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 15, fontWeight: 700, fontVariantNumeric: 'tabular-nums',
|
|
371
|
+
background: `rgba(0, 229, 255, ${(0.06 + 0.45 * Math.sqrt(v / max)).toFixed(3)})`, color: 'var(--sd-fg-1, #eaf6f8)',
|
|
372
|
+
// An outline, not a shadow: Analyst takes every shadow off, and these mark the hidden giants and the warm S.
|
|
373
|
+
outline: hot ? `2px solid ${TIER_COLORS.S}` : warm ? '2px solid #FF7043' : 'none', outlineOffset: -2,
|
|
374
|
+
}}>{fmt(v)}</div>
|
|
375
|
+
);
|
|
376
|
+
})}
|
|
377
|
+
</div>
|
|
378
|
+
))}
|
|
379
|
+
</div>
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// The small pieces the Insights tab is built from: tier chips with their
|
|
4
|
+
// letter on them (C and D are two greys too close to tell apart by colour),
|
|
5
|
+
// rarity tags, the 1st / 2nd chip, the person cell, cards and the line under
|
|
6
|
+
// a number that says where it came from. Colours are the look's own: the
|
|
7
|
+
// theme's dot colours (lib/themes.js TIER_COLORS, a live object) and the
|
|
8
|
+
// --sd-* variables, so a light look reads as well as the dark ones.
|
|
9
|
+
|
|
10
|
+
import { useEffect, useState } from 'react';
|
|
11
|
+
import Link from 'next/link';
|
|
12
|
+
import Avatar from '../Avatar';
|
|
13
|
+
import { TIER_COLORS, luminance } from '../../../lib/themes';
|
|
14
|
+
import { RARITY } from '../../../lib/rarity';
|
|
15
|
+
import { roleLine } from '../../../lib/insights-board';
|
|
16
|
+
import { markRequested } from '../../../lib/requests-client';
|
|
17
|
+
import ConnectChoice, { secondaryLook } from '../AutoConnect';
|
|
18
|
+
|
|
19
|
+
export const fmt = (n) => Number(n || 0).toLocaleString('en-US');
|
|
20
|
+
export const one = (x) => (Math.round(Number(x || 0) * 10) / 10).toFixed(1);
|
|
21
|
+
export const pc = (x) => `${Math.round(Number(x) || 0)}%`;
|
|
22
|
+
export const RARITY_BY_KEY = Object.fromEntries(RARITY.map((r) => [r.key, r]));
|
|
23
|
+
export const LINE = 'rgba(var(--sd-ink, 255, 255, 255), 0.08)';
|
|
24
|
+
export const SOFT_LINE = 'rgba(var(--sd-ink, 255, 255, 255), 0.05)';
|
|
25
|
+
export const CARD_BG = 'rgba(var(--sd-ink, 255, 255, 255), 0.035)';
|
|
26
|
+
|
|
27
|
+
export function useIsMobile() {
|
|
28
|
+
const [m, setM] = useState(false);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const check = () => setM(window.innerWidth < 768);
|
|
31
|
+
check();
|
|
32
|
+
window.addEventListener('resize', check);
|
|
33
|
+
return () => window.removeEventListener('resize', check);
|
|
34
|
+
}, []);
|
|
35
|
+
return m;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Black or white, whichever reads on a colour. */
|
|
39
|
+
export function inkOn(hex) {
|
|
40
|
+
try { return luminance(hex) > 0.36 ? '#000' : '#fff'; } catch { return '#000'; }
|
|
41
|
+
}
|
|
42
|
+
export const tierColor = (tier) => TIER_COLORS[tier] || '#888888';
|
|
43
|
+
|
|
44
|
+
/** A tier as a filled chip with its letter. */
|
|
45
|
+
export function TierChip({ tier, size = 20 }) {
|
|
46
|
+
if (!tier) return null;
|
|
47
|
+
const c = tierColor(tier);
|
|
48
|
+
return (
|
|
49
|
+
<span aria-label={`${tier} tier`} style={{
|
|
50
|
+
display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
|
|
51
|
+
minWidth: size, height: size, padding: '0 4px', borderRadius: Math.round(size / 4),
|
|
52
|
+
background: c, color: inkOn(c), fontSize: Math.round(size * 0.56), fontWeight: 800, lineHeight: 1,
|
|
53
|
+
}}>{tier}</span>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** "1st" or "2nd". */
|
|
58
|
+
export function DegreeChip({ degree }) {
|
|
59
|
+
const first = degree === 1;
|
|
60
|
+
return (
|
|
61
|
+
<span style={{
|
|
62
|
+
fontSize: 10.5, fontWeight: 700, padding: '2px 6px', borderRadius: 4, letterSpacing: 0.2, flexShrink: 0, whiteSpace: 'nowrap',
|
|
63
|
+
background: first ? 'rgba(0,255,136,0.1)' : 'rgba(255,107,53,0.1)',
|
|
64
|
+
border: `1px solid ${first ? 'rgba(0,255,136,0.3)' : 'rgba(255,107,53,0.3)'}`,
|
|
65
|
+
color: first ? 'var(--sd-green, #7dffc0)' : 'var(--sd-orange, #ffab8a)',
|
|
66
|
+
}}>{first ? '1st' : '2nd'}</span>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** How rare the way in is, and the count: "+" where it's the ways your scans saw (a floor). */
|
|
71
|
+
export function RarityTag({ rarity, count = true, small = false }) {
|
|
72
|
+
if (!rarity) return null;
|
|
73
|
+
const r = RARITY_BY_KEY[rarity.key];
|
|
74
|
+
const title = rarity.from === 'linkedin'
|
|
75
|
+
? `${fmt(rarity.count)} mutual connection${rarity.count === 1 ? '' : 's'}, LinkedIn’s own count`
|
|
76
|
+
: `${fmt(rarity.count)} way${rarity.count === 1 ? '' : 's'} in seen in your scans, which can only go up`;
|
|
77
|
+
return (
|
|
78
|
+
<span title={title} style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: small ? 11 : 11.5, color: 'var(--sd-fg-2, #b8c4c4)', whiteSpace: 'nowrap' }}>
|
|
79
|
+
<i className="sd-dot-html" style={{ width: 8, height: 8, borderRadius: '50%', background: r.color, flexShrink: 0 }} />
|
|
80
|
+
{r.label}
|
|
81
|
+
{count && <span style={{ color: 'var(--sd-fg-4, #6f7a88)', fontSize: 10.5 }}>{fmt(rarity.count)}{rarity.from === 'linkedin' ? '' : '+'}</span>}
|
|
82
|
+
</span>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Face, name, tier and the role their score is built on. */
|
|
87
|
+
export function PersonCell({ row, size = 34, chip = true, sub }) {
|
|
88
|
+
const { title, company } = roleLine(row);
|
|
89
|
+
return (
|
|
90
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
|
|
91
|
+
<Avatar person={row} size={size} tierColors={TIER_COLORS} />
|
|
92
|
+
<span style={{ display: 'flex', flexDirection: 'column', minWidth: 0 }}>
|
|
93
|
+
<b style={{ fontSize: 13.5, fontWeight: 650, color: 'var(--sd-fg-1, #fff)', ...ELLIPSIS }}>{row?.name || 'Someone'}</b>
|
|
94
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 5, fontSize: 11.5, color: 'var(--sd-fg-3, #8b9a9a)', minWidth: 0 }}>
|
|
95
|
+
{chip && <TierChip tier={row?.tier} size={15} />}
|
|
96
|
+
<span style={ELLIPSIS}>{sub ?? [title, company].filter(Boolean).join(' · ')}</span>
|
|
97
|
+
</span>
|
|
98
|
+
</span>
|
|
99
|
+
</span>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const ELLIPSIS = { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0 };
|
|
104
|
+
|
|
105
|
+
/** The page's title, in the tab's own colours (plain ink on Analyst, which takes gradients off). */
|
|
106
|
+
export const TITLE = {
|
|
107
|
+
margin: 0, fontSize: 34, fontWeight: 850, letterSpacing: -0.6, lineHeight: 1.1,
|
|
108
|
+
background: 'linear-gradient(135deg, var(--sd-gold, #FFD700) 10%, var(--sd-orange, #FF6B35) 60%, var(--sd-purple, #9B59B6))',
|
|
109
|
+
WebkitBackgroundClip: 'text', backgroundClip: 'text', WebkitTextFillColor: 'transparent',
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/** A board's title and the one line under it. */
|
|
113
|
+
export function Hero({ title, children, isMobile, right }) {
|
|
114
|
+
return (
|
|
115
|
+
<div style={{ display: 'flex', flexDirection: isMobile ? 'column' : 'row', justifyContent: 'space-between', alignItems: isMobile ? 'stretch' : 'flex-end', gap: isMobile ? 10 : 24, marginBottom: 16 }}>
|
|
116
|
+
<div style={{ minWidth: 0 }}>
|
|
117
|
+
<h1 style={{ ...TITLE, fontSize: isMobile ? 27 : 34 }}>{title}</h1>
|
|
118
|
+
<p style={{ margin: '5px 0 0', color: 'var(--sd-fg-2, #b8c4c4)', fontSize: isMobile ? 13 : 14, lineHeight: 1.45 }}>{children}</p>
|
|
119
|
+
</div>
|
|
120
|
+
{right}
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** A card, as the rail and the boards draw them. */
|
|
126
|
+
export function Card({ children, style, dashed = false, id }) {
|
|
127
|
+
return (
|
|
128
|
+
<section id={id} style={{
|
|
129
|
+
border: dashed ? '1px dashed rgba(255,215,0,0.35)' : `1px solid ${LINE}`, borderRadius: 14,
|
|
130
|
+
background: dashed ? 'rgba(255,215,0,0.04)' : CARD_BG, padding: '13px 14px 10px', minWidth: 0, ...style,
|
|
131
|
+
}}>{children}</section>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** A card's title, its line under it, and a link to its board. */
|
|
136
|
+
export function CardHead({ title, sub, more, onMore }) {
|
|
137
|
+
return (
|
|
138
|
+
<>
|
|
139
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 8 }}>
|
|
140
|
+
<h3 style={{ margin: 0, fontSize: 14, fontWeight: 750, color: 'var(--sd-fg-1, #fff)' }}>{title}</h3>
|
|
141
|
+
{more && <button type="button" onClick={onMore} style={LINK_BTN}>{more}</button>}
|
|
142
|
+
</div>
|
|
143
|
+
{sub && <div style={{ fontSize: 11.5, color: 'var(--sd-fg-3, #8b9a9a)', margin: '2px 0 6px', lineHeight: 1.4 }}>{sub}</div>}
|
|
144
|
+
</>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const LINK_BTN = { background: 'none', border: 'none', padding: 0, cursor: 'pointer', fontSize: 11.5, color: 'var(--sd-blue, #3498DB)', whiteSpace: 'nowrap', fontWeight: 600 };
|
|
149
|
+
|
|
150
|
+
/** Where a number came from, under it. */
|
|
151
|
+
export function Src({ children, style }) {
|
|
152
|
+
if (!children) return null;
|
|
153
|
+
return <div style={{ fontSize: 11, color: 'var(--sd-fg-4, #6f7a88)', lineHeight: 1.5, marginTop: 6, ...style }}>{children}</div>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** A row in a rail card: rank, who, and a number on the right with what it counts. */
|
|
157
|
+
export function MiniRow({ rank, left, value, valueColor, label, first }) {
|
|
158
|
+
return (
|
|
159
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 9, padding: '6px 0', borderTop: first ? 'none' : `1px solid ${SOFT_LINE}` }}>
|
|
160
|
+
{rank != null && <span style={{ width: 14, fontSize: 11.5, color: 'var(--sd-fg-4, #6f7a88)', fontWeight: 700, flexShrink: 0 }}>{rank}</span>}
|
|
161
|
+
<span style={{ flex: 1, minWidth: 0 }}>{left}</span>
|
|
162
|
+
<span style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', textAlign: 'right', flexShrink: 0 }}>
|
|
163
|
+
<b style={{ fontSize: 15, fontWeight: 800, color: valueColor || 'var(--sd-fg-1, #fff)', fontVariantNumeric: 'tabular-nums' }}>{value}</b>
|
|
164
|
+
{label && <span style={{ fontSize: 10, color: 'var(--sd-fg-4, #6f7a88)', whiteSpace: 'nowrap' }}>{label}</span>}
|
|
165
|
+
</span>
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** A board that needs circles, on a network with none yet. */
|
|
171
|
+
export function NeedsCircles({ source, compact = false }) {
|
|
172
|
+
const csv = source === 'csv';
|
|
173
|
+
return (
|
|
174
|
+
<div style={{ fontSize: compact ? 12 : 13, color: 'var(--sd-fg-2, #b8c4c4)', lineHeight: 1.5, padding: compact ? '4px 0 6px' : '6px 0' }}>
|
|
175
|
+
{csv
|
|
176
|
+
? 'A CSV import is your connections only: LinkedIn’s export has no circles. Scan your own network to see who stands behind your connections.'
|
|
177
|
+
: 'Scan a few circles to see who stands behind your connections.'}
|
|
178
|
+
{!csv && source === 'own' && <> <Link href="/setup" style={{ color: 'var(--sd-green, #00ff88)', fontWeight: 700, textDecoration: 'none', whiteSpace: 'nowrap' }}>Open Scan →</Link></>}
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const ACT = {
|
|
184
|
+
display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 12, fontWeight: 700, padding: '6px 11px',
|
|
185
|
+
borderRadius: 7, whiteSpace: 'nowrap', textDecoration: 'none', cursor: 'pointer', flexShrink: 0,
|
|
186
|
+
};
|
|
187
|
+
const OUTLINE = { ...ACT, color: 'var(--sd-blue, #3498DB)', border: '1px solid rgba(52,152,219,0.4)', background: 'rgba(52,152,219,0.06)' };
|
|
188
|
+
|
|
189
|
+
/** Their circle in Bridge Chains: the link every page uses (/?chain=<id>). */
|
|
190
|
+
export function OpenCircle({ id, compact }) {
|
|
191
|
+
return <Link href={`/?chain=${encodeURIComponent(id)}`} style={{ ...OUTLINE, ...(compact ? { padding: '4px 8px', fontSize: 11 } : null) }}>Open circle</Link>;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** The Scan page's "Scan one circle" for them (/setup?scan=<id>): it says the cost and asks first. */
|
|
195
|
+
export function ScanCircle({ id, compact }) {
|
|
196
|
+
return <Link href={`/setup?scan=${encodeURIComponent(id)}`} style={{ ...OUTLINE, ...(compact ? { padding: '4px 8px', fontSize: 11 } : null) }} title="Opens the Scan page with them picked: it says what the scan costs and starts nothing until you press it">Scan circle</Link>;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Ask: what the person card's Connect does. Their LinkedIn profile opens in
|
|
201
|
+
* a new tab to send the request there, and it's marked sent through the
|
|
202
|
+
* connection who is your best way in (lib/requests-client.js), everywhere at once.
|
|
203
|
+
* On your own network Auto stands first, the better pick, and Ask steps back
|
|
204
|
+
* to an outline beside it, as everywhere else Connect is offered
|
|
205
|
+
* (components/AutoConnect.js; Blake, 2026-10-03: "wherever its available").
|
|
206
|
+
*/
|
|
207
|
+
export function AskButton({ row, bridgeId, asked, compact, canAuto = false }) {
|
|
208
|
+
if (asked) {
|
|
209
|
+
return <span title="A request is marked sent. The next check of your own connections notices when they accept." style={{ ...ACT, cursor: 'default', color: 'var(--sd-fg-4, #6f7a88)', border: `1px dashed ${LINE}`, ...(compact ? { padding: '4px 8px', fontSize: 11 } : null) }}>Asked</span>;
|
|
210
|
+
}
|
|
211
|
+
if (!row?.profile_url) return null;
|
|
212
|
+
const size = compact ? { padding: '4px 9px', fontSize: 11 } : null;
|
|
213
|
+
const ask = (second) => (
|
|
214
|
+
<a href={row.profile_url} target="_blank" rel="noopener noreferrer"
|
|
215
|
+
title="Opens their LinkedIn profile in a new tab to send the request there, and marks it sent here"
|
|
216
|
+
onClick={() => { markRequested(row, { bridgeId }).catch(() => {}); }}
|
|
217
|
+
style={second
|
|
218
|
+
? { ...ACT, ...secondaryLook(compact ? 'row' : 'table'), ...size }
|
|
219
|
+
: { ...ACT, color: '#000', border: 'none', background: 'linear-gradient(135deg, #FF6B35, #FFD700)', ...size }}>
|
|
220
|
+
Ask
|
|
221
|
+
</a>
|
|
222
|
+
);
|
|
223
|
+
return <ConnectChoice person={row} canAuto={canAuto} size={compact ? 'row' : 'table'} connect={ask} />;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** A horizontal bar made of parts: [{ value, color, label? }] over `max`. */
|
|
227
|
+
export function Bars({ parts, max, height = 14, gap = 2 }) {
|
|
228
|
+
const total = Math.max(1, max || parts.reduce((t, p) => t + p.value, 0));
|
|
229
|
+
return (
|
|
230
|
+
<span style={{ display: 'flex', gap, height, background: 'rgba(var(--sd-ink, 255, 255, 255), 0.05)', borderRadius: 4, overflow: 'hidden', minWidth: 0, flex: 1 }}>
|
|
231
|
+
{parts.filter((p) => p.value > 0).map((p, i) => (
|
|
232
|
+
<span key={i} title={p.title} style={{
|
|
233
|
+
width: `${(100 * p.value) / total}%`, background: p.color, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
234
|
+
fontSize: 10, fontWeight: 800, color: inkOn(p.ink || '#888888'), overflow: 'hidden', whiteSpace: 'nowrap',
|
|
235
|
+
}}>{p.label && (100 * p.value) / total >= 5 ? p.label : ''}</span>
|
|
236
|
+
))}
|
|
237
|
+
</span>
|
|
238
|
+
);
|
|
239
|
+
}
|