six-degrees 0.7.0 → 0.8.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_0is4qoc.js → lib_rpc_0t552z6.js} +1 -1
- 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_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 +3 -3
- 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/0838_s6w-i_ik.js +1 -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/standalone/.next/static/chunks/112mv-com-mqa.js +21 -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/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 +60 -0
- package/.next/standalone/README.md +25 -1
- package/.next/standalone/SECURITY.md +8 -0
- package/.next/standalone/app/api/outreach/route.js +13 -73
- package/.next/standalone/app/api/scraper/route.js +97 -4
- 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 +16 -9
- 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/scraper-client.js +18 -5
- package/.next/standalone/lib/settings.js +3 -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/scrape.py +569 -9
- package/.next/static/chunks/02wnf8dkxegxg.js +34 -0
- package/.next/static/chunks/0838_s6w-i_ik.js +1 -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/static/chunks/112mv-com-mqa.js +21 -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/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 +60 -0
- package/README.md +25 -1
- package/SECURITY.md +8 -0
- 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/scraper-client.js +18 -5
- package/lib/settings.js +3 -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 → 3NCyJyAPDCEYsYTwF1Ieu}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_ssgManifest.js +0 -0
- /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_buildManifest.js +0 -0
- /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_ssgManifest.js +0 -0
|
@@ -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
|
+
}
|
|
@@ -19,7 +19,7 @@ import Link from 'next/link';
|
|
|
19
19
|
import { Section, Body, LINE } from '../ui';
|
|
20
20
|
import {
|
|
21
21
|
AUTO, PEOPLE_PER_SEARCH, DANGER_AT, REPORTED_MONTH, LEVELS, usageLevel, warningParts, estimate,
|
|
22
|
-
barMax, pacificText, whenText, untilText, agoText, hoursText,
|
|
22
|
+
barMax, pacificText, whenText, untilText, agoText, hoursText, INVITE_CAPS, REPORTED_WEEKLY_INVITES,
|
|
23
23
|
} from '../../../lib/usage';
|
|
24
24
|
import { RESTRICTED_AT, RISKY_DAILY, SAFE_LIMITS } from '../../../lib/search-risk';
|
|
25
25
|
import { paceOf, searchesPerHour } from '../../../lib/scan-pace';
|
|
@@ -100,6 +100,51 @@ function Meter({ title, used, of, max, color, ticks = [], band, children }) {
|
|
|
100
100
|
);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Auto's connection requests (lib/auto-connect.js): the last 24 hours and the
|
|
105
|
+
* last 7 days, each against its cap, one bar under the other. Its own caps,
|
|
106
|
+
* not the search budget: a request is the one thing Six Degrees sends.
|
|
107
|
+
*/
|
|
108
|
+
function InviteMeter({ day, week, freesAt, weekFreesAt, now }) {
|
|
109
|
+
const known = Number.isFinite(day) && Number.isFinite(week);
|
|
110
|
+
const bars = [
|
|
111
|
+
{ label: 'Last 24 hours', used: day, cap: INVITE_CAPS.day },
|
|
112
|
+
{ label: 'Last 7 days', used: week, cap: INVITE_CAPS.week },
|
|
113
|
+
];
|
|
114
|
+
const left = known ? Math.max(0, Math.min(INVITE_CAPS.day - day, INVITE_CAPS.week - week)) : null;
|
|
115
|
+
return (
|
|
116
|
+
<div style={{ marginTop: 18 }} data-usage="invites">
|
|
117
|
+
<div style={{ fontSize: 13.5, fontWeight: 650, color: 'var(--sd-fg-1, #e8e8ee)' }}>Connection requests (Auto)</div>
|
|
118
|
+
{bars.map((b) => (
|
|
119
|
+
<div key={b.label} style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 8 }}>
|
|
120
|
+
<span style={{ flex: '0 0 88px', fontSize: 12, color: 'var(--sd-fg-3, #8b9a9a)' }}>{b.label}</span>
|
|
121
|
+
<div style={{ position: 'relative', flex: 1, height: 10, borderRadius: 5, background: 'rgba(var(--sd-ink, 255, 255, 255), 0.07)' }}>
|
|
122
|
+
<div style={{
|
|
123
|
+
position: 'absolute', top: 0, bottom: 0, left: 0, borderRadius: 5, transition: 'width 0.4s ease',
|
|
124
|
+
width: Number.isFinite(b.used) ? at(b.used, Math.max(b.cap, b.used)) : 0,
|
|
125
|
+
background: b.used >= b.cap ? '#ff6b6b' : 'linear-gradient(90deg, #FFD700, #FF6B35)',
|
|
126
|
+
}} />
|
|
127
|
+
</div>
|
|
128
|
+
<span style={{ flex: '0 0 auto', fontSize: 13, color: 'var(--sd-fg-3, #8b9a9a)', fontVariantNumeric: 'tabular-nums', minWidth: 58, textAlign: 'right' }}>
|
|
129
|
+
<b style={{ fontSize: 15, color: 'var(--sd-fg-1, #fff)' }}>{count(b.used)}</b> of {b.cap}
|
|
130
|
+
</span>
|
|
131
|
+
</div>
|
|
132
|
+
))}
|
|
133
|
+
<div style={{ fontSize: 12.5, color: 'var(--sd-fg-3, #8b9a9a)', lineHeight: 1.6, marginTop: 6 }}>
|
|
134
|
+
{!known ? null
|
|
135
|
+
: freesAt ? <>Auto’s {INVITE_CAPS.day} a day are used. The next one frees {whenAndUntil(freesAt, now)}.</>
|
|
136
|
+
: weekFreesAt ? <>Auto’s {INVITE_CAPS.week} a week are used. The next one frees {whenAndUntil(weekFreesAt, now)}.</>
|
|
137
|
+
: <>{plural(left, 'request')} left for Auto now.</>}
|
|
138
|
+
<div style={small}>
|
|
139
|
+
Each press of Auto sends one request from the scanner’s Chrome, without a note, and opens their
|
|
140
|
+
profile once (a profile view). It stops at {INVITE_CAPS.day} in any 24 hours and {INVITE_CAPS.week} in any 7 days.
|
|
141
|
+
LinkedIn doesn’t publish its invitation limit; people commonly report about {REPORTED_WEEKLY_INVITES} a week.
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
103
148
|
function Row({ label, children }) {
|
|
104
149
|
return (
|
|
105
150
|
<div style={{ display: 'flex', gap: '4px 16px', flexWrap: 'wrap', padding: '10px 0', borderTop: LINE, fontSize: 13 }}>
|
|
@@ -260,6 +305,8 @@ export default function UsageSection() {
|
|
|
260
305
|
</div>
|
|
261
306
|
</Meter>
|
|
262
307
|
|
|
308
|
+
<InviteMeter day={u.invitesToday} week={u.invitesWeek} freesAt={u.invitesFreeAt} weekFreesAt={u.invitesWeekFreeAt} now={now} />
|
|
309
|
+
|
|
263
310
|
<div style={{ marginTop: 22 }}>
|
|
264
311
|
<Row label="Speed">
|
|
265
312
|
{pace.label}: about {searchesPerHour(limits.pace)} searches an hour at most while a scan runs.
|
|
@@ -301,8 +348,8 @@ export default function UsageSection() {
|
|
|
301
348
|
</div>
|
|
302
349
|
|
|
303
350
|
<div style={{ ...small, marginTop: 14, paddingTop: 12, borderTop: LINE }}>
|
|
304
|
-
Where these numbers come from: Six Degrees writes down every search
|
|
305
|
-
LinkedIn account, on this computer, and counts them here. Searches you make yourself on linkedin.com
|
|
351
|
+
Where these numbers come from: Six Degrees writes down every search, profile view and Auto request it makes
|
|
352
|
+
on your LinkedIn account, on this computer, and counts them here. Searches you make yourself on linkedin.com
|
|
306
353
|
aren’t in it, and LinkedIn shows no count of its own. The {RESTRICTED_AT} searches and the 20 to 25
|
|
307
354
|
profile views are what happened to one real account, not safe limits. Change the budget on the
|
|
308
355
|
{' '}<Link href="/setup" style={link}>Scan page</Link>, under Fine-tune the scanner. Nothing here is sent anywhere.
|
|
@@ -107,13 +107,12 @@ html:has([data-field-question]) {
|
|
|
107
107
|
border-radius: 3px;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
/* Where the scan status bar sits: just under the header (app/components/ScanStatusBar.js). */
|
|
111
|
-
:root { --scan-bar-top: 94px; }
|
|
112
|
-
@media (max-width: 767px) { :root { --scan-bar-top: 66px; } }
|
|
113
110
|
|
|
114
111
|
/* The notch's running dot (app/components/ScanStatusBar.js). */
|
|
115
112
|
@keyframes notch-pulse { 50% { opacity: 0.35; } }
|
|
116
113
|
@media (prefers-reduced-motion: no-preference) { .notch-pulse { animation: notch-pulse 1.6s ease-in-out infinite; } }
|
|
114
|
+
@keyframes notch-in { from { opacity: 0; } }
|
|
115
|
+
@media (prefers-reduced-motion: no-preference) { .notch-in { animation: notch-in 0.25s ease-out; } }
|
|
117
116
|
|
|
118
117
|
/* The Social tab's CRM (app/social/Crm.js): the list and the person side by
|
|
119
118
|
side, stacked on a phone. In Pipeline the board takes the whole width and
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// Insights: your network ranked by power, and the boards that read it
|
|
4
|
+
// (Blake, 2026-10-03: "stats and insights … data and insights based off their
|
|
5
|
+
// circles … almost no moral tie to the person … base it off value like
|
|
6
|
+
// richest person or highest power people … an intro perspective look at the
|
|
7
|
+
// data of what we have"). Mock-up B, "The Power Index", is the page; mock-up
|
|
8
|
+
// C, the scrolling report, is its Report view.
|
|
9
|
+
//
|
|
10
|
+
// Its views sit in the notch under the header, as every page's do
|
|
11
|
+
// (lib/island.js setNotchTabs): People, Kingmakers, Gatekeepers, Companies,
|
|
12
|
+
// Industries and Report. Every number comes from lib/insights-board.js, which
|
|
13
|
+
// says where each came from; nothing here is estimated and nothing is about
|
|
14
|
+
// money.
|
|
15
|
+
|
|
16
|
+
import { Suspense, useCallback, useEffect, useMemo, useState } from 'react';
|
|
17
|
+
import { useSearchParams } from 'next/navigation';
|
|
18
|
+
import Link from 'next/link';
|
|
19
|
+
import AppHeader from '../components/AppHeader';
|
|
20
|
+
import OnboardingGate from '../components/OnboardingGate';
|
|
21
|
+
import { useUser } from '../components/UserProvider';
|
|
22
|
+
import useRequests from '../components/useRequests';
|
|
23
|
+
import { setNotchTabs } from '../../lib/island';
|
|
24
|
+
import { loadNetwork } from '../../lib/network';
|
|
25
|
+
import { loadCsvNetwork } from '../../lib/csv';
|
|
26
|
+
import { loadScanNotes, NO_SCAN_NOTES } from '../../lib/scraper-client';
|
|
27
|
+
import { hasRequest } from '../../lib/requests-client';
|
|
28
|
+
import { networkLevel } from '../../lib/level';
|
|
29
|
+
import { IS_DEMO } from '../../lib/demo';
|
|
30
|
+
import {
|
|
31
|
+
boardBase, powerIndex, kingmakers, gatekeepers, hiddenGiants, tierByRarity, untapped,
|
|
32
|
+
companyPower, industries, titleLadder, coverage, howSure, reachSummary,
|
|
33
|
+
} from '../../lib/insights-board';
|
|
34
|
+
import PeopleView from '../components/insights/PeopleView';
|
|
35
|
+
import { KingmakersBoard, GatekeepersBoard, CompaniesBoard, IndustriesBoard } from '../components/insights/Boards';
|
|
36
|
+
import Report from '../components/insights/Report';
|
|
37
|
+
import { fmt, useIsMobile, TITLE } from '../components/insights/parts';
|
|
38
|
+
|
|
39
|
+
const VIEWS = ['people', 'kingmakers', 'gatekeepers', 'companies', 'industries', 'report'];
|
|
40
|
+
const LABELS = { people: 'People', kingmakers: 'Kingmakers', gatekeepers: 'Gatekeepers', companies: 'Companies', industries: 'Industries', report: 'Report' };
|
|
41
|
+
|
|
42
|
+
export default function InsightsPage() {
|
|
43
|
+
// Suspense because the page reads the address's ?view= (useSearchParams),
|
|
44
|
+
// which Next requires to sit inside one for the page to build (TRAPS §41).
|
|
45
|
+
return <OnboardingGate><Suspense><InsightsInner /></Suspense></OnboardingGate>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function InsightsInner() {
|
|
49
|
+
const { userId } = useUser();
|
|
50
|
+
const isMobile = useIsMobile();
|
|
51
|
+
const asked = useSearchParams().get('view');
|
|
52
|
+
const [view, setView] = useState(() => (VIEWS.includes(asked) ? asked : 'people'));
|
|
53
|
+
const [net, setNet] = useState(null); // { degree1, degree2, degree3, notes, source, budget }
|
|
54
|
+
const [failed, setFailed] = useState(null);
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (IS_DEMO || !userId) return;
|
|
58
|
+
let live = true;
|
|
59
|
+
(async () => {
|
|
60
|
+
try {
|
|
61
|
+
// The sample (this window) or a kept CSV import, else your own network:
|
|
62
|
+
// lib/csv.js openNetworkSource, the one rule every page opens on.
|
|
63
|
+
const local = await loadCsvNetwork();
|
|
64
|
+
if (local) {
|
|
65
|
+
if (live) setNet({ degree1: local.degree1 || [], degree2: local.degree2 || [], degree3: [], notes: NO_SCAN_NOTES, source: local.source || 'csv', budget: null });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Your daily search budget and speed, for what scanning the next circles would take:
|
|
69
|
+
// ?usage only reads the scanner's files (no checks, no Python, nothing written).
|
|
70
|
+
const usage = fetch('/api/scraper?usage').then((r) => (r.ok ? r.json() : null)).catch(() => null);
|
|
71
|
+
const [n, notes, used] = await Promise.all([loadNetwork(userId), loadScanNotes(), usage]);
|
|
72
|
+
const limits = used?.limits;
|
|
73
|
+
if (live) setNet({ ...n, notes, source: 'own', budget: limits ? { daily: limits.daily, pace: limits.pace } : null });
|
|
74
|
+
} catch (e) {
|
|
75
|
+
if (live) setFailed(e.message || 'The network couldn’t be loaded.');
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
78
|
+
return () => { live = false; };
|
|
79
|
+
}, [userId]);
|
|
80
|
+
|
|
81
|
+
// The quick part first: the ranking and the boards that read circles.
|
|
82
|
+
const base = useMemo(() => (net ? boardBase(net) : null), [net]);
|
|
83
|
+
const data = useMemo(() => {
|
|
84
|
+
if (!base) return null;
|
|
85
|
+
const index = powerIndex(base);
|
|
86
|
+
return {
|
|
87
|
+
index, reach: reachSummary(base), kings: kingmakers(base), gates: gatekeepers(base), hidden: hiddenGiants(base),
|
|
88
|
+
grid: tierByRarity(base), cover: coverage(base, net.budget || {}), sure: howSure(index),
|
|
89
|
+
};
|
|
90
|
+
}, [base, net]);
|
|
91
|
+
// Companies and titles read every headline (about half a second on 7,000
|
|
92
|
+
// people), so they follow once the table is on screen.
|
|
93
|
+
const [slow, setSlow] = useState(null);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (!base) return undefined;
|
|
96
|
+
const t = setTimeout(() => {
|
|
97
|
+
const companies = companyPower(base);
|
|
98
|
+
setSlow({ base, companies, industries: industries(companies), ladder: titleLadder(base) });
|
|
99
|
+
}, 30);
|
|
100
|
+
return () => clearTimeout(t);
|
|
101
|
+
}, [base]);
|
|
102
|
+
const heavy = slow && slow.base === base ? slow : null;
|
|
103
|
+
|
|
104
|
+
const requests = useRequests();
|
|
105
|
+
const isAsked = useCallback((row) => hasRequest(row, requests), [requests]);
|
|
106
|
+
const open = useMemo(() => (base ? untapped(base, isAsked) : null), [base, isAsked]);
|
|
107
|
+
|
|
108
|
+
const pick = useCallback((v) => {
|
|
109
|
+
setView(v);
|
|
110
|
+
try {
|
|
111
|
+
const u = new URL(window.location.href);
|
|
112
|
+
if (v === 'people') u.searchParams.delete('view'); else u.searchParams.set('view', v);
|
|
113
|
+
window.history.replaceState(null, '', u.pathname + u.search);
|
|
114
|
+
} catch { /* the address stays */ }
|
|
115
|
+
window.scrollTo(0, 0);
|
|
116
|
+
}, []);
|
|
117
|
+
|
|
118
|
+
// The views in the notch, with how many each ranks.
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (!data || data.index.total === 0) { setNotchTabs(null); return undefined; }
|
|
121
|
+
const count = {
|
|
122
|
+
people: data.index.total, kingmakers: data.kings.scanned, gatekeepers: data.gates.list.length,
|
|
123
|
+
companies: heavy?.companies.count, industries: heavy?.industries.list.length,
|
|
124
|
+
};
|
|
125
|
+
setNotchTabs({
|
|
126
|
+
items: VIEWS.map((key) => ({
|
|
127
|
+
key,
|
|
128
|
+
label: count[key] ? <>{LABELS[key]}<small style={{ fontSize: 10.5, opacity: 0.65, fontWeight: 600 }}>{fmt(count[key])}</small></> : LABELS[key],
|
|
129
|
+
})),
|
|
130
|
+
current: view, onPick: pick,
|
|
131
|
+
});
|
|
132
|
+
return () => setNotchTabs(null);
|
|
133
|
+
}, [data, heavy, view, pick]);
|
|
134
|
+
|
|
135
|
+
const level = useMemo(() => (net ? networkLevel(net.degree1, net.degree2) : null), [net]);
|
|
136
|
+
const csvMode = Boolean(net && net.source !== 'own');
|
|
137
|
+
|
|
138
|
+
if (IS_DEMO) {
|
|
139
|
+
return (
|
|
140
|
+
<div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 12, color: 'var(--sd-fg-2, #ccc)', fontFamily: 'var(--sd-font)' }}>
|
|
141
|
+
<h1 style={{ fontSize: 20, fontWeight: 700, margin: 0, color: 'var(--sd-fg-1, #fff)' }}>Not part of the demo</h1>
|
|
142
|
+
<p style={{ fontSize: 13, margin: 0 }}>The public demo includes the Network Circle and Degrees views only.</p>
|
|
143
|
+
<Link href="/" style={{ fontSize: 13, color: 'var(--sd-fg-3, #888)', textDecoration: 'none' }}>← Back to the network</Link>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const shared = { data, heavy, open, net, source: net?.source, isMobile, onView: pick };
|
|
149
|
+
return (
|
|
150
|
+
<div style={{ minHeight: '100vh', background: 'var(--sd-page)', color: 'var(--sd-fg-1, #fff)', fontFamily: 'var(--sd-font)' }}>
|
|
151
|
+
<AppHeader active="insights" csvMode={csvMode} level={level} isMobile={isMobile} />
|
|
152
|
+
{/* Room for the notch, which hangs under the header with the views in it */}
|
|
153
|
+
<main style={{ maxWidth: view === 'report' ? 920 : 1400, margin: '0 auto', padding: isMobile ? '52px 12px 0' : '58px 30px 0' }}>
|
|
154
|
+
{failed ? <Message title="The network couldn’t be loaded" body={failed} />
|
|
155
|
+
: !data ? <Message title="Reading your network…" />
|
|
156
|
+
: data.index.total === 0 ? <Empty source={net.source} />
|
|
157
|
+
: view === 'people' ? <PeopleView {...shared} requests={requests} />
|
|
158
|
+
: view === 'kingmakers' ? <KingmakersBoard {...shared} />
|
|
159
|
+
: view === 'gatekeepers' ? <GatekeepersBoard {...shared} />
|
|
160
|
+
: view === 'companies' ? <CompaniesBoard {...shared} />
|
|
161
|
+
: view === 'industries' ? <IndustriesBoard {...shared} />
|
|
162
|
+
: <Report {...shared} />}
|
|
163
|
+
<Footer net={net} data={data} isMobile={isMobile} />
|
|
164
|
+
</main>
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function Message({ title, body }) {
|
|
170
|
+
return (
|
|
171
|
+
<div style={{ padding: '80px 0', textAlign: 'center' }}>
|
|
172
|
+
<div style={{ fontSize: 17, fontWeight: 700 }}>{title}</div>
|
|
173
|
+
{body && <div style={{ fontSize: 13, color: 'var(--sd-fg-3, #888)', marginTop: 8 }}>{body}</div>}
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** No one to rank yet: where people come from. */
|
|
179
|
+
function Empty({ source }) {
|
|
180
|
+
return (
|
|
181
|
+
<div style={{ maxWidth: 560, margin: '60px auto', textAlign: 'center' }}>
|
|
182
|
+
<h1 style={TITLE}>The Power Index</h1>
|
|
183
|
+
<p style={{ fontSize: 15, color: 'var(--sd-fg-2, #b8c4c4)', lineHeight: 1.6, margin: '12px 0 22px' }}>
|
|
184
|
+
Nothing to rank yet. Once your connections are in, everyone within two steps of you shows here, ranked by power,
|
|
185
|
+
with who holds the doors and what’s still closed.
|
|
186
|
+
</p>
|
|
187
|
+
<div style={{ display: 'flex', gap: 10, justifyContent: 'center', flexWrap: 'wrap' }}>
|
|
188
|
+
{source === 'own' && (
|
|
189
|
+
<Link href="/setup" style={{ padding: '10px 16px', borderRadius: 8, fontWeight: 700, fontSize: 13.5, textDecoration: 'none', color: '#000', background: 'linear-gradient(135deg, #00ff88, #1abc9c)' }}>
|
|
190
|
+
Scan your connections →
|
|
191
|
+
</Link>
|
|
192
|
+
)}
|
|
193
|
+
<Link href="/import" style={{ padding: '10px 16px', borderRadius: 8, fontWeight: 700, fontSize: 13.5, textDecoration: 'none', color: 'var(--sd-fg-1, #fff)', border: '1px solid rgba(var(--sd-ink, 255, 255, 255), 0.2)' }}>
|
|
194
|
+
Import a CSV
|
|
195
|
+
</Link>
|
|
196
|
+
</div>
|
|
197
|
+
<p style={{ fontSize: 12.5, color: 'var(--sd-fg-4, #6f7a88)', marginTop: 18 }}>
|
|
198
|
+
Or look around first: the <Link href="/" style={{ color: 'var(--sd-blue, #3498DB)', textDecoration: 'none' }}>map’s welcome screen</Link> opens a sample network where every person is invented.
|
|
199
|
+
</p>
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** The rule every screen with a tier says, and which network this is. */
|
|
205
|
+
function Footer({ net, data, isMobile }) {
|
|
206
|
+
const what = !net || !data?.index.total ? null
|
|
207
|
+
: net.source === 'sample' ? 'The sample network: every person in it is invented.'
|
|
208
|
+
: net.source === 'csv' ? `Your CSV import: ${fmt(data?.reach.d1)} connections, no circles.`
|
|
209
|
+
: data ? `Counted from your ${fmt(data.reach.d1)} connections and the ${fmt(data.reach.circles)} circle${data.reach.circles === 1 ? '' : 's'} you’ve scanned.` : null;
|
|
210
|
+
return (
|
|
211
|
+
<footer style={{
|
|
212
|
+
margin: '36px 0 0', padding: '18px 0 34px', borderTop: '1px solid rgba(var(--sd-ink, 255, 255, 255), 0.08)',
|
|
213
|
+
display: 'flex', flexDirection: isMobile ? 'column' : 'row', justifyContent: 'space-between', gap: isMobile ? 8 : 20,
|
|
214
|
+
color: 'var(--sd-fg-4, #6f7a88)', fontSize: 11.5, lineHeight: 1.5,
|
|
215
|
+
}}>
|
|
216
|
+
<div><b style={{ color: 'var(--sd-fg-2, #b8c4c4)' }}>Power ranks reachability, not people.</b> A tier is a statement about network position, not human worth.</div>
|
|
217
|
+
{what && <div>{what}</div>}
|
|
218
|
+
</footer>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
@@ -4,8 +4,6 @@ import { Suspense, useEffect, useState, useRef, useMemo, useCallback } from 'rea
|
|
|
4
4
|
import { useSearchParams } from 'next/navigation';
|
|
5
5
|
import { fillsCircles, loadScanNotes, NO_SCAN_NOTES } from '../lib/scraper-client';
|
|
6
6
|
import useScanner from './components/useScanner';
|
|
7
|
-
import useRequests from './components/useRequests';
|
|
8
|
-
import { requestCount } from '../lib/requests-client';
|
|
9
7
|
import { loadNetwork } from '../lib/network';
|
|
10
8
|
import { resolveView } from './components/views';
|
|
11
9
|
import { peopleByDegree } from '../lib/degrees';
|
|
@@ -105,7 +103,6 @@ function HomeInner() {
|
|
|
105
103
|
const [scanNotes, setScanNotes] = useState(NO_SCAN_NOTES);
|
|
106
104
|
const shapeRef = useRef('');
|
|
107
105
|
// Everyone with a request out, shared with every view (lib/requests-client.js).
|
|
108
|
-
const pendingCount = requestCount(useRequests());
|
|
109
106
|
|
|
110
107
|
useEffect(() => {
|
|
111
108
|
if (!userId) return;
|
|
@@ -305,20 +302,7 @@ function HomeInner() {
|
|
|
305
302
|
</button>
|
|
306
303
|
</div>
|
|
307
304
|
)}
|
|
308
|
-
|
|
309
|
-
{/* Pending count badge — in header: everyone with a request out, once each (lib/requests-client.js) */}
|
|
310
|
-
{isDegreesMode && !csvMode && pendingCount > 0 && (
|
|
311
|
-
<Link href="/queue" style={{
|
|
312
|
-
padding: '5px 12px', borderRadius: 16, fontSize: 12, fontWeight: 600, textDecoration: 'none',
|
|
313
|
-
background: 'rgba(255,107,53,0.12)', color: 'var(--sd-orange, #FF6B35)',
|
|
314
|
-
border: '1px solid rgba(255,107,53,0.25)',
|
|
315
|
-
display: 'inline-flex', alignItems: 'center', gap: 5,
|
|
316
|
-
}}>
|
|
317
|
-
<span style={{ fontSize: 10 }}>⏳</span>
|
|
318
|
-
{pendingCount} Pending
|
|
319
|
-
</Link>
|
|
320
|
-
)}
|
|
321
|
-
</AppHeader>
|
|
305
|
+
/>
|
|
322
306
|
|
|
323
307
|
<div style={{ display: 'flex', height: isMobile ? 'calc(100vh - 70px)' : 'calc(100vh - 130px)', position: 'relative' }}>
|
|
324
308
|
<FilterPanel
|