six-degrees 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (427) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
  3. package/.next/standalone/.next/build-manifest.json +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +35 -7
  5. package/.next/standalone/.next/required-server-files.json +6 -1
  6. package/.next/standalone/.next/routes-manifest.json +18 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.rsc +11 -11
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +11 -11
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +10 -10
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  21. package/.next/standalone/.next/server/app/api/admin-delete/route.js +1 -1
  22. package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/admin-update/route.js +1 -1
  24. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/bridges/route.js +1 -1
  26. package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/bulk-import/route.js +1 -1
  28. package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/connections/route.js +1 -1
  32. package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/delete-cluster/route.js +1 -1
  39. package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/ingest/route.js +1 -1
  41. package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/network/route.js +1 -1
  43. package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/notifications/route.js +1 -1
  45. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/outreach/route.js +3 -3
  47. package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/api/queue/route.js +1 -1
  49. package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/api/scraper/route.js +1 -1
  51. package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/api/setup-profile/route.js +1 -1
  56. package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/api/unlock/route.js +1 -1
  58. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  60. package/.next/standalone/.next/server/app/api/update-images/route.js +1 -1
  61. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/api/users/route.js +1 -1
  63. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  64. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/import.html +1 -1
  68. package/.next/standalone/.next/server/app/import.rsc +13 -13
  69. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +13 -13
  70. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
  71. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +12 -12
  72. package/.next/standalone/.next/server/app/index.html +1 -1
  73. package/.next/standalone/.next/server/app/index.rsc +13 -13
  74. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +12 -12
  75. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +13 -13
  76. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  77. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  78. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  79. package/.next/standalone/.next/server/app/launch.html +2 -2
  80. package/.next/standalone/.next/server/app/launch.rsc +13 -13
  81. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +13 -13
  82. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
  83. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +12 -12
  84. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  85. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  86. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  87. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  88. package/.next/standalone/.next/server/app/paths.html +1 -1
  89. package/.next/standalone/.next/server/app/paths.rsc +13 -13
  90. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +13 -13
  91. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
  92. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +12 -12
  93. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  94. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  95. package/.next/standalone/.next/server/app/profile.html +1 -1
  96. package/.next/standalone/.next/server/app/profile.rsc +13 -13
  97. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +13 -13
  98. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
  99. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +12 -12
  100. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  101. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  102. package/.next/standalone/.next/server/app/queue.html +1 -1
  103. package/.next/standalone/.next/server/app/queue.rsc +13 -13
  104. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +13 -13
  105. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
  106. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +12 -12
  107. package/.next/standalone/.next/server/app/scores/page/app-paths-manifest.json +3 -0
  108. package/.next/standalone/.next/server/app/scores/page/build-manifest.json +20 -0
  109. package/.next/standalone/.next/server/app/scores/page/next-font-manifest.json +11 -0
  110. package/.next/standalone/.next/server/app/scores/page/react-loadable-manifest.json +1 -0
  111. package/.next/standalone/.next/server/app/scores/page/server-reference-manifest.json +4 -0
  112. package/.next/standalone/.next/server/app/scores/page.js +13 -0
  113. package/.next/standalone/.next/server/app/scores/page.js.map +5 -0
  114. package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -0
  115. package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +3 -0
  116. package/.next/standalone/.next/server/app/scores.html +1 -0
  117. package/.next/standalone/.next/server/app/scores.meta +26 -0
  118. package/.next/standalone/.next/server/app/scores.rsc +24 -0
  119. package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +24 -0
  120. package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +4 -0
  121. package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +28 -0
  122. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  123. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  124. package/.next/standalone/.next/server/app/settings.html +1 -1
  125. package/.next/standalone/.next/server/app/settings.rsc +13 -13
  126. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +13 -13
  127. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  128. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +12 -12
  129. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  130. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  131. package/.next/standalone/.next/server/app/setup.html +1 -1
  132. package/.next/standalone/.next/server/app/setup.rsc +13 -13
  133. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +13 -13
  134. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
  135. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +12 -12
  136. package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
  137. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +1 -1
  138. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +1 -1
  139. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0i5hdu4._.js → [root-of-the-server]__03ydxxh._.js} +2 -2
  140. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +1 -1
  141. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0bat8nx._.js +8 -0
  142. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +1 -1
  143. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +1 -1
  144. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljcioj._.js +1 -1
  145. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +1 -1
  147. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +1 -1
  148. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
  149. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uag-k5._.js +1 -1
  150. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0um1uey._.js +1 -1
  151. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +1 -1
  152. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +1 -1
  153. package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +1 -1
  154. package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +1 -1
  155. package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +1 -1
  156. package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +1 -1
  157. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +1 -1
  158. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +2 -2
  159. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +1 -1
  160. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +1 -1
  161. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
  162. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +1 -1
  163. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +1 -1
  164. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +1 -1
  165. package/.next/standalone/.next/server/chunks/_02twlca._.js +10 -0
  166. package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
  167. package/.next/standalone/.next/server/chunks/_0lw8_i0._.js +1 -1
  168. package/.next/standalone/.next/server/chunks/_1_fynia._.js +8 -6
  169. package/.next/standalone/.next/server/chunks/_1fn30_l._.js +1 -1
  170. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
  171. package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
  172. package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +13 -8
  173. package/.next/standalone/.next/server/chunks/{lib_rpc_02ndsg2.js → lib_rpc_03i8xef.js} +1 -1
  174. package/.next/standalone/.next/server/chunks/{lib_rpc_08q5vp6.js → lib_rpc_08fc3rp.js} +1 -1
  175. package/.next/standalone/.next/server/chunks/{lib_rpc_00tx2sm.js → lib_rpc_08r2t_q.js} +1 -1
  176. package/.next/standalone/.next/server/chunks/{lib_rpc_02-qa4d.js → lib_rpc_0btpv1t.js} +1 -1
  177. package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +5 -5
  178. package/.next/standalone/.next/server/chunks/lib_rpc_0cm2ev3.js +3 -0
  179. package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +3 -0
  180. package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +3 -0
  181. package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +3 -0
  182. package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +3 -0
  183. package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +3 -0
  184. package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +3 -0
  185. package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +3 -0
  186. package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +3 -0
  187. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0an6blh._.js → [root-of-the-server]__0zzcbqg._.js} +2 -2
  188. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1194jps._.js +3 -0
  189. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1z1j3ek._.js → [root-of-the-server]__19ip1qq._.js} +2 -2
  190. package/.next/standalone/.next/server/chunks/ssr/_01f38ty._.js +3 -0
  191. package/.next/standalone/.next/server/chunks/ssr/_06w_gl4._.js +3 -0
  192. package/.next/standalone/.next/server/chunks/ssr/_0hw_qnj._.js +3 -0
  193. package/.next/standalone/.next/server/chunks/ssr/_13kzrwv._.js +3 -0
  194. package/.next/standalone/.next/server/chunks/ssr/_15xuwkv._.js +3 -0
  195. package/.next/standalone/.next/server/chunks/ssr/_1cyxocj._.js +3 -0
  196. package/.next/standalone/.next/server/chunks/ssr/_1gpbbw9._.js +3 -0
  197. package/.next/standalone/.next/server/chunks/ssr/_1hgd5dp._.js +3 -0
  198. package/.next/standalone/.next/server/chunks/ssr/_1vpfyd-._.js +3 -0
  199. package/.next/standalone/.next/server/chunks/ssr/_20w06i-._.js +3 -0
  200. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_scores_page_actions_0x-w58n.js +3 -0
  201. package/.next/standalone/.next/server/chunks/ssr/app_components_settings_SectorPicker_1c-29qw.js +3 -0
  202. package/.next/standalone/.next/server/chunks/ssr/app_launch_page_19t0dyd.js +1 -1
  203. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +11 -9
  204. package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +4 -0
  205. package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
  206. package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +5 -5
  207. package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +3 -0
  208. package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
  209. package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +2 -2
  210. package/.next/standalone/.next/server/chunks/ssr/lib_1bn38gs._.js +3 -0
  211. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0x3i8za._.js +131 -0
  212. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_1bunoni._.js → node_modules_next_dist_1v8aef8._.js} +2 -2
  213. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_client_components_server-async-storage_0t1dato.js +3 -0
  214. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  215. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  216. package/.next/standalone/.next/server/next-font-manifest.js +1 -1
  217. package/.next/standalone/.next/server/next-font-manifest.json +4 -0
  218. package/.next/standalone/.next/server/pages/404.html +1 -1
  219. package/.next/standalone/.next/server/pages/500.html +1 -1
  220. package/.next/standalone/.next/server/prefetch-hints.json +14 -0
  221. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  222. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  223. package/.next/standalone/.next/static/chunks/0ax_9zzboyv6d.js +44 -0
  224. package/.next/standalone/.next/static/chunks/0cuuo2jwc9h94.js +1 -0
  225. package/.next/standalone/.next/static/chunks/0l1568qff0efj.js +1 -0
  226. package/.next/standalone/.next/static/chunks/0muwt9bykzr-1.js +1 -0
  227. package/.next/standalone/.next/static/chunks/0w76iraw7ij3k.js +18 -0
  228. package/.next/standalone/.next/static/chunks/135n87ydxjrra.js +2 -0
  229. package/.next/standalone/.next/static/chunks/18xu0fbgo_qz-.js +1 -0
  230. package/.next/standalone/.next/static/chunks/28bu26ausqzmn.js +1 -0
  231. package/.next/standalone/.next/static/chunks/{40nvbgfn7-072.css → 2p6o90z0vgcwr.css} +1 -1
  232. package/.next/standalone/.next/static/chunks/2pnbm1z6j7xab.js +1 -0
  233. package/.next/standalone/.next/static/chunks/2xjho0lroiwlq.js +1 -0
  234. package/.next/standalone/.next/static/chunks/3e8zg6ctvvb8p.js +33 -0
  235. package/.next/standalone/.next/static/chunks/3qh78g2aqhprd.js +2 -0
  236. package/.next/standalone/CHANGELOG.md +286 -0
  237. package/.next/standalone/CONTRIBUTING.md +15 -4
  238. package/.next/standalone/README.md +50 -29
  239. package/.next/standalone/SECURITY.md +10 -0
  240. package/.next/standalone/app/api/company-scores/legacy/route.js +1 -1
  241. package/.next/standalone/app/api/company-scores/route.js +1 -1
  242. package/.next/standalone/app/api/ingest/route.js +23 -2
  243. package/.next/standalone/app/api/outreach/route.js +107 -44
  244. package/.next/standalone/app/api/scraper/route.js +146 -17
  245. package/.next/standalone/app/api/settings/sector-preview/route.js +3 -2
  246. package/.next/standalone/app/api/settings/sector-suggestions/route.js +1 -1
  247. package/.next/standalone/app/api/update-images/route.js +41 -7
  248. package/.next/standalone/app/avatars/[file]/route.js +6 -1
  249. package/.next/standalone/app/components/Avatar.js +5 -2
  250. package/.next/standalone/app/components/ChainView.js +659 -469
  251. package/.next/standalone/app/components/CompanyScores.js +16 -10
  252. package/.next/standalone/app/components/EmptyState.js +1 -1
  253. package/.next/standalone/app/components/FieldStep.js +94 -0
  254. package/.next/standalone/app/components/ForceGraph.js +183 -768
  255. package/.next/standalone/app/components/GridView.js +9 -7
  256. package/.next/standalone/app/components/LegacyScoresCard.js +1 -1
  257. package/.next/standalone/app/components/LinkedInLimits.js +22 -7
  258. package/.next/standalone/app/components/ListView.js +6 -4
  259. package/.next/standalone/app/components/MappingProgress.js +5 -6
  260. package/.next/standalone/app/components/OnboardingGate.js +1 -1
  261. package/.next/standalone/app/components/OrbitGraph.js +33 -1
  262. package/.next/standalone/app/components/OutlinkQuest.js +5 -4
  263. package/.next/standalone/app/components/PathsAnalyzer.js +1 -1
  264. package/.next/standalone/app/components/RingsView.js +9 -8
  265. package/.next/standalone/app/components/SeparationView.js +96 -14
  266. package/.next/standalone/app/components/Sidebar.js +366 -287
  267. package/.next/standalone/app/components/TheirCircle.js +300 -0
  268. package/.next/standalone/app/components/settings/SectorPicker.js +202 -0
  269. package/.next/standalone/app/components/settings/SectorSection.js +21 -195
  270. package/.next/standalone/app/components/settings/TierSection.js +99 -0
  271. package/.next/standalone/app/components/useRequests.js +12 -0
  272. package/.next/standalone/app/components/useScanner.js +13 -0
  273. package/.next/standalone/app/components/views.js +3 -5
  274. package/.next/standalone/app/globals.css +7 -0
  275. package/.next/standalone/app/launch/page.js +6 -3
  276. package/.next/standalone/app/page.js +183 -74
  277. package/.next/standalone/app/paths/page.js +20 -14
  278. package/.next/standalone/app/profile/page.js +3 -3
  279. package/.next/standalone/app/queue/page.js +56 -60
  280. package/.next/standalone/app/scores/page.js +72 -0
  281. package/.next/standalone/app/settings/page.js +15 -3
  282. package/.next/standalone/app/setup/page.js +423 -260
  283. package/.next/standalone/db/schema.js +2 -1
  284. package/.next/standalone/eslint.config.mjs +3 -3
  285. package/.next/standalone/install.sh +3 -3
  286. package/.next/standalone/lib/chain-layout.js +139 -0
  287. package/.next/standalone/lib/circle.js +169 -0
  288. package/.next/standalone/lib/companies.js +1 -1
  289. package/.next/standalone/lib/csv.js +21 -10
  290. package/.next/standalone/lib/data-export.js +2 -1
  291. package/.next/standalone/lib/data-folder.js +4 -2
  292. package/.next/standalone/lib/db-client.js +1 -0
  293. package/.next/standalone/lib/galaxy.js +12 -0
  294. package/.next/standalone/lib/ingest.js +27 -8
  295. package/.next/standalone/lib/legacy-offer.js +7 -6
  296. package/.next/standalone/lib/legacy-scores.js +2 -2
  297. package/.next/standalone/lib/linkedin-limits.js +30 -8
  298. package/.next/standalone/lib/photos.js +47 -0
  299. package/.next/standalone/lib/quest.js +8 -3
  300. package/.next/standalone/lib/rarity.js +80 -0
  301. package/.next/standalone/lib/reach.js +113 -0
  302. package/.next/standalone/lib/requests-client.js +113 -0
  303. package/.next/standalone/lib/rpc.js +26 -15
  304. package/.next/standalone/lib/scan-state.js +1 -0
  305. package/.next/standalone/lib/scanner-setup.js +20 -1
  306. package/.next/standalone/lib/score-guess.js +60 -0
  307. package/.next/standalone/lib/scoring.js +1761 -69
  308. package/.next/standalone/lib/scraper-client.js +235 -17
  309. package/.next/standalone/lib/sector-directory.js +1 -1
  310. package/.next/standalone/lib/sector-focus.js +17 -3
  311. package/.next/standalone/lib/separation.js +4 -2
  312. package/.next/standalone/lib/settings-client.js +21 -0
  313. package/.next/standalone/lib/settings-effects.js +19 -1
  314. package/.next/standalone/lib/settings.js +7 -2
  315. package/.next/standalone/next.config.mjs +25 -3
  316. package/.next/standalone/package-lock.json +2 -2
  317. package/.next/standalone/package.json +2 -2
  318. package/.next/standalone/public/demo-data.json +1 -1
  319. package/.next/standalone/scripts/build-app.mjs +11 -2
  320. package/.next/standalone/scripts/check-node.mjs +1 -1
  321. package/.next/standalone/scripts/gen-synthetic.mjs +1 -1
  322. package/.next/standalone/scripts/image_store.py +77 -21
  323. package/.next/standalone/scripts/scrape.py +305 -29
  324. package/.next/standalone/server.js +1 -1
  325. package/.next/static/chunks/0ax_9zzboyv6d.js +44 -0
  326. package/.next/static/chunks/0cuuo2jwc9h94.js +1 -0
  327. package/.next/static/chunks/0l1568qff0efj.js +1 -0
  328. package/.next/static/chunks/0muwt9bykzr-1.js +1 -0
  329. package/.next/static/chunks/0w76iraw7ij3k.js +18 -0
  330. package/.next/static/chunks/135n87ydxjrra.js +2 -0
  331. package/.next/static/chunks/18xu0fbgo_qz-.js +1 -0
  332. package/.next/static/chunks/28bu26ausqzmn.js +1 -0
  333. package/.next/static/chunks/{40nvbgfn7-072.css → 2p6o90z0vgcwr.css} +1 -1
  334. package/.next/static/chunks/2pnbm1z6j7xab.js +1 -0
  335. package/.next/static/chunks/2xjho0lroiwlq.js +1 -0
  336. package/.next/static/chunks/3e8zg6ctvvb8p.js +33 -0
  337. package/.next/static/chunks/3qh78g2aqhprd.js +2 -0
  338. package/CHANGELOG.md +286 -0
  339. package/README.md +50 -29
  340. package/SECURITY.md +10 -0
  341. package/db/schema.js +2 -1
  342. package/lib/chain-layout.js +139 -0
  343. package/lib/circle.js +169 -0
  344. package/lib/companies.js +1 -1
  345. package/lib/csv.js +21 -10
  346. package/lib/data-export.js +2 -1
  347. package/lib/data-folder.js +4 -2
  348. package/lib/db-client.js +1 -0
  349. package/lib/galaxy.js +12 -0
  350. package/lib/ingest.js +27 -8
  351. package/lib/legacy-offer.js +7 -6
  352. package/lib/legacy-scores.js +2 -2
  353. package/lib/linkedin-limits.js +30 -8
  354. package/lib/photos.js +47 -0
  355. package/lib/quest.js +8 -3
  356. package/lib/rarity.js +80 -0
  357. package/lib/reach.js +113 -0
  358. package/lib/requests-client.js +113 -0
  359. package/lib/rpc.js +26 -15
  360. package/lib/scan-state.js +1 -0
  361. package/lib/scanner-setup.js +20 -1
  362. package/lib/score-guess.js +60 -0
  363. package/lib/scoring.js +1761 -69
  364. package/lib/scraper-client.js +235 -17
  365. package/lib/sector-directory.js +1 -1
  366. package/lib/sector-focus.js +17 -3
  367. package/lib/separation.js +4 -2
  368. package/lib/settings-client.js +21 -0
  369. package/lib/settings-effects.js +19 -1
  370. package/lib/settings.js +7 -2
  371. package/package.json +2 -2
  372. package/public/demo-data.json +1 -1
  373. package/scripts/check-node.mjs +1 -1
  374. package/scripts/image_store.py +77 -21
  375. package/scripts/scrape.py +305 -29
  376. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1t3rugi._.js +0 -3
  377. package/.next/standalone/.next/server/chunks/lib_rpc_0_8i3ft.js +0 -3
  378. package/.next/standalone/.next/server/chunks/lib_rpc_0huvw-3.js +0 -3
  379. package/.next/standalone/.next/server/chunks/lib_rpc_0ip2vdh.js +0 -3
  380. package/.next/standalone/.next/server/chunks/lib_rpc_0jzisgh.js +0 -3
  381. package/.next/standalone/.next/server/chunks/lib_rpc_0yi9xvd.js +0 -3
  382. package/.next/standalone/.next/server/chunks/lib_rpc_1jyhd0o.js +0 -3
  383. package/.next/standalone/.next/server/chunks/lib_rpc_1kn59-i.js +0 -3
  384. package/.next/standalone/.next/server/chunks/lib_rpc_1q2jkza.js +0 -3
  385. package/.next/standalone/.next/server/chunks/lib_rpc_1vpm1sk.js +0 -3
  386. package/.next/standalone/.next/server/chunks/lib_rpc_1x6p_31.js +0 -3
  387. package/.next/standalone/.next/server/chunks/ssr/_07u8byv._.js +0 -3
  388. package/.next/standalone/.next/server/chunks/ssr/_08r_mlf._.js +0 -3
  389. package/.next/standalone/.next/server/chunks/ssr/_0942v8n._.js +0 -3
  390. package/.next/standalone/.next/server/chunks/ssr/_0hl_t7r._.js +0 -3
  391. package/.next/standalone/.next/server/chunks/ssr/_0tb83dw._.js +0 -132
  392. package/.next/standalone/.next/server/chunks/ssr/_1-87bs1._.js +0 -3
  393. package/.next/standalone/.next/server/chunks/ssr/_17gui6a._.js +0 -3
  394. package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +0 -3
  395. package/.next/standalone/.next/server/chunks/ssr/node_modules_16vay6l._.js +0 -3
  396. package/.next/standalone/.next/server/chunks/ssr/node_modules_1kciwn4._.js +0 -3
  397. package/.next/standalone/.next/static/chunks/06x3jiiz96m5t.js +0 -1
  398. package/.next/standalone/.next/static/chunks/08k1crid-05dt.js +0 -1
  399. package/.next/standalone/.next/static/chunks/0ngx9s6-zgshe.js +0 -33
  400. package/.next/standalone/.next/static/chunks/0q-_khndfxubv.js +0 -2
  401. package/.next/standalone/.next/static/chunks/1c79veqedvubt.js +0 -1
  402. package/.next/standalone/.next/static/chunks/1chp3nx45meg5.js +0 -44
  403. package/.next/standalone/.next/static/chunks/1irqj0lf-fu3d.js +0 -1
  404. package/.next/standalone/.next/static/chunks/1uadqylrod5_v.js +0 -1
  405. package/.next/standalone/.next/static/chunks/23u0zclgwpc4d.js +0 -1
  406. package/.next/standalone/.next/static/chunks/289ktj97di6zg.js +0 -1
  407. package/.next/standalone/.next/static/chunks/2b3lmf8e6uj8m.js +0 -16
  408. package/.next/standalone/.next/static/chunks/3npq5yd7c4dmn.js +0 -2
  409. package/.next/standalone/app/components/BridgeRing.js +0 -753
  410. package/.next/static/chunks/06x3jiiz96m5t.js +0 -1
  411. package/.next/static/chunks/08k1crid-05dt.js +0 -1
  412. package/.next/static/chunks/0ngx9s6-zgshe.js +0 -33
  413. package/.next/static/chunks/0q-_khndfxubv.js +0 -2
  414. package/.next/static/chunks/1c79veqedvubt.js +0 -1
  415. package/.next/static/chunks/1chp3nx45meg5.js +0 -44
  416. package/.next/static/chunks/1irqj0lf-fu3d.js +0 -1
  417. package/.next/static/chunks/1uadqylrod5_v.js +0 -1
  418. package/.next/static/chunks/23u0zclgwpc4d.js +0 -1
  419. package/.next/static/chunks/289ktj97di6zg.js +0 -1
  420. package/.next/static/chunks/2b3lmf8e6uj8m.js +0 -16
  421. package/.next/static/chunks/3npq5yd7c4dmn.js +0 -2
  422. /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
  423. /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
  424. /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
  425. /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
  426. /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
  427. /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
@@ -1,753 +0,0 @@
1
- 'use client';
2
-
3
- /**
4
- * BridgeRing — the rotary dial, ported from the v2 build.
5
- *
6
- * Your highest-leverage connections sit on a ring around you. Drag anywhere to
7
- * spin it (1:1, no snapping while the pointer is down); release and the nearest
8
- * bridge snaps to the top slot. That bridge's known 2nd-degree circle fans
9
- * outward from the top inside a soft, deformable boundary, revealed node by
10
- * node.
11
- *
12
- * Rotation is fully imperative: the angle lives in a ref and positions are
13
- * written through d3-selection each frame, so React never re-renders during a
14
- * drag or a tween. It re-renders only when a selection is committed.
15
- */
16
-
17
- import { useEffect, useMemo, useRef, useState } from 'react';
18
- import { select } from 'd3';
19
- import { TIER_COLORS, initialsFor } from '../../lib/tiers';
20
-
21
- // Twelve is what fits at full size, with a name and a circle-power figure under
22
- // each. Beyond that the dial goes dense: smaller nodes, and only the selected
23
- // one is labelled — so the capacity comes from the geometry of the ring rather
24
- // than from a number someone picked. A network with thirty-nine S-tier bridges
25
- // should show thirty-nine, not page four times to prove they exist.
26
- export const MAX_BRIDGES = 12;
27
- const DENSE_NODE_R = 9;
28
- const DENSE_GAP = 7;
29
- const MAX_CLUSTER_NODES = 24;
30
- const TOP = -Math.PI / 2;
31
- const SNAP_MS = 350;
32
- const CLICK_SLOP_PX = 6;
33
- const HULL_PAD_PX = 18;
34
-
35
- function normalizeAngle(a) {
36
- let x = a % (Math.PI * 2);
37
- if (x > Math.PI) x -= Math.PI * 2;
38
- if (x < -Math.PI) x += Math.PI * 2;
39
- return x;
40
- }
41
-
42
- /** Andrew's monotone chain convex hull. */
43
- function convexHull(points) {
44
- const pts = [...points].sort((a, b) => a.x - b.x || a.y - b.y);
45
- if (pts.length <= 2) return pts;
46
- const cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
47
- const lower = [];
48
- for (const p of pts) {
49
- while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], p) <= 0) lower.pop();
50
- lower.push(p);
51
- }
52
- const upper = [];
53
- for (let i = pts.length - 1; i >= 0; i--) {
54
- const p = pts[i];
55
- while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], p) <= 0) upper.pop();
56
- upper.push(p);
57
- }
58
- lower.pop();
59
- upper.pop();
60
- return lower.concat(upper);
61
- }
62
-
63
- /** Closed Catmull-Rom spline through the points, emitted as cubic beziers. */
64
- function catmullRomClosed(points) {
65
- const n = points.length;
66
- if (n < 3) return '';
67
- let d = `M ${points[0].x.toFixed(1)} ${points[0].y.toFixed(1)}`;
68
- for (let i = 0; i < n; i++) {
69
- const p0 = points[(i - 1 + n) % n];
70
- const p1 = points[i];
71
- const p2 = points[(i + 1) % n];
72
- const p3 = points[(i + 2) % n];
73
- const c1x = p1.x + (p2.x - p0.x) / 6;
74
- const c1y = p1.y + (p2.y - p0.y) / 6;
75
- const c2x = p2.x - (p3.x - p1.x) / 6;
76
- const c2y = p2.y - (p3.y - p1.y) / 6;
77
- d += ` C ${c1x.toFixed(1)} ${c1y.toFixed(1)}, ${c2x.toFixed(1)} ${c2y.toFixed(1)}, ${p2.x.toFixed(1)} ${p2.y.toFixed(1)}`;
78
- }
79
- return `${d} Z`;
80
- }
81
-
82
- function shortName(name) {
83
- const s = String(name || '');
84
- if (s.length <= 16) return s;
85
- const parts = s.trim().split(/\s+/).filter(Boolean);
86
- if (parts.length < 2) return `${s.slice(0, 15)}…`;
87
- return `${parts[0]} ${parts[parts.length - 1].charAt(0)}.`;
88
- }
89
-
90
- function useIsMobile() {
91
- const [mobile, setMobile] = useState(false);
92
- useEffect(() => {
93
- const check = () => setMobile(window.innerWidth < 768);
94
- check();
95
- window.addEventListener('resize', check);
96
- return () => window.removeEventListener('resize', check);
97
- }, []);
98
- return mobile;
99
- }
100
-
101
- export default function BridgeRing({ connections = [], degree2 = [], onSelect, userName = 'You', userImage = null }) {
102
- const containerRef = useRef(null);
103
- const svgRef = useRef(null);
104
- const offsetRef = useRef(0);
105
- const tweenRef = useRef(null);
106
- const interactedRef = useRef(false);
107
- const cbRef = useRef({ onSelect });
108
- const [size, setSize] = useState({ w: 0, h: 0 });
109
- const [selectedId, setSelectedId] = useState(null);
110
- // The dial holds twelve. A thirteenth bridge used to be silently unreachable;
111
- // this is the window's start into the full sorted list.
112
- const [ringStart, setRingStart] = useState(0);
113
- const isMobile = useIsMobile();
114
-
115
- useEffect(() => { cbRef.current = { onSelect }; }, [onSelect]);
116
-
117
- // The dial is about circles you have actually mapped, so bridges with a known
118
- // 2nd degree come first. Without any, it still renders your strongest people
119
- // rather than an empty ring that reads as broken.
120
- const { bridges, clusters } = useMemo(() => {
121
- const byBridge = new Map();
122
- for (const c of degree2) {
123
- const key = c.source_connection_id;
124
- if (!key) continue;
125
- const list = byBridge.get(key);
126
- if (list) list.push(c);
127
- else byBridge.set(key, [c]);
128
- }
129
- const withCircle = connections
130
- .filter((c) => byBridge.has(c.id))
131
- .sort((a, b) => (Number(b.circle_power) || 0) - (Number(a.circle_power) || 0));
132
- const chosen = withCircle.length
133
- ? withCircle
134
- : [...connections].sort((a, b) => (Number(b.power_score) || 0) - (Number(a.power_score) || 0));
135
- return { bridges: chosen, clusters: byBridge };
136
- }, [connections, degree2]);
137
-
138
- // Depend on content, not array identity — the parent rebuilds these arrays on
139
- // every render, and rebuilding the dial mid-drag would fight the pointer.
140
- const signature = useMemo(
141
- () => `${bridges.map((b) => b.id).join(',')}|${degree2.length}|${ringStart}`,
142
- [bridges, degree2, ringStart]
143
- );
144
- const dataRef = useRef({ bridges, clusters });
145
- useEffect(() => { dataRef.current = { bridges, clusters }; }, [bridges, clusters]);
146
-
147
- useEffect(() => {
148
- if (!selectedId && bridges.length) setSelectedId(bridges[0].id);
149
- }, [selectedId, bridges]);
150
-
151
- useEffect(() => {
152
- const el = containerRef.current;
153
- if (!el) return undefined;
154
- // Synchronous first measure — first paint must not wait on ResizeObserver.
155
- const measure = () => {
156
- const rect = el.getBoundingClientRect();
157
- setSize((prev) => {
158
- const w = Math.round(rect.width);
159
- const h = Math.round(rect.height);
160
- return prev.w === w && prev.h === h ? prev : { w, h };
161
- });
162
- };
163
- measure();
164
- const ro = new ResizeObserver(measure);
165
- ro.observe(el);
166
- window.addEventListener('resize', measure);
167
- return () => {
168
- ro.disconnect();
169
- window.removeEventListener('resize', measure);
170
- };
171
- }, []);
172
-
173
- useEffect(() => {
174
- const svgEl = svgRef.current;
175
- const { w, h } = size;
176
- if (!svgEl || w < 80 || h < 80) return undefined;
177
- const { bridges: ringAll, clusters: clusterMap } = dataRef.current;
178
- const total = ringAll.length;
179
- if (total === 0) return undefined;
180
- // Ring geometry first — the capacity below is derived from it.
181
- const baseR = isMobile ? 150 : 230;
182
- const panelW = isMobile ? 0 : 372;
183
- const bandBase = isMobile ? 56 : 70;
184
- const bandGap = isMobile ? 32 : 40;
185
- const bandSpan = bandBase + 2 * bandGap;
186
- const R = Math.max(90, Math.min(baseR, (h - bandSpan - 84) / 2, (w - panelW) / 2 - 56));
187
- const cx = isMobile ? w / 2 : Math.max(R + 60, (w - panelW) / 2);
188
- const cy = Math.max(R + bandSpan + 38, Math.min(h - R - 46, h * (isMobile ? 0.42 : 0.55)));
189
-
190
- // Capacity comes from the ring, not from a number someone picked.
191
- //
192
- // A hard cap at twelve meant a network with thirty-nine S-tier bridges paged
193
- // four times to prove they existed, and a hard switch to "dense" made
194
- // fourteen bridges tiny when they would have fit at full size. Both are the
195
- // same mistake: a threshold standing in for a measurement.
196
- //
197
- // Fit as many as the circumference allows at the smallest readable size,
198
- // then let the node size scale back up when there is room to spare.
199
- const capacity = Math.max(
200
- MAX_BRIDGES,
201
- Math.floor((2 * Math.PI * R) / (DENSE_NODE_R * 2 + DENSE_GAP))
202
- );
203
-
204
- // Discrete pages, not a sliding wrap: a window that wraps produced labels
205
- // like "13-24 of 14", which is not a range anyone can hold in their head.
206
- const pageCount = Math.max(1, Math.ceil(total / capacity));
207
- const page = ((ringStart % pageCount) + pageCount) % pageCount;
208
- const start = page * capacity;
209
- const ring = total > capacity ? ringAll.slice(start, start + capacity) : ringAll;
210
- const n = ring.length;
211
- if (n === 0) return undefined;
212
-
213
- const svg = select(svgEl);
214
- svg.selectAll('*').remove();
215
- svg.attr('viewBox', `0 0 ${w} ${h}`);
216
- svgEl.style.cursor = 'grab';
217
-
218
- const step = (Math.PI * 2) / n;
219
- const slot = (i) => TOP + i * step;
220
-
221
- // Arc length between neighbours decides both size and whether names fit.
222
- const spacing = (2 * Math.PI * R) / Math.max(1, n);
223
- const nodeR = Math.max(DENSE_NODE_R, Math.min(26, spacing / 2.6));
224
- // A name plus a circle-power figure needs roughly this much room; below it,
225
- // only the selected bridge is labelled rather than overlapping every name.
226
- const labelsFit = spacing > 70;
227
- let selIndex = ring.findIndex((b) => b.id === selectedId);
228
- if (selIndex < 0) selIndex = 0;
229
- const selected = ring[selIndex];
230
-
231
- const defs = svg.append('defs');
232
- defs.append('filter').attr('id', 'bridge-cloud-blur')
233
- .attr('x', '-40%').attr('y', '-40%').attr('width', '180%').attr('height', '180%')
234
- .append('feGaussianBlur').attr('stdDeviation', 5);
235
- const nebula = defs.append('radialGradient').attr('id', 'bridge-nebula')
236
- .attr('cx', '50%').attr('cy', '50%').attr('r', '50%');
237
- nebula.append('stop').attr('offset', '0%').attr('stop-color', 'currentColor').attr('stop-opacity', 0.22);
238
- nebula.append('stop').attr('offset', '60%').attr('stop-color', 'currentColor').attr('stop-opacity', 0.06);
239
- nebula.append('stop').attr('offset', '100%').attr('stop-color', 'currentColor').attr('stop-opacity', 0);
240
-
241
- // Initials always render; an image draws on top and hides itself on error,
242
- // so a dead avatar is never a broken-image glyph.
243
- function avatar(g, opts) {
244
- g.append('circle').attr('r', opts.r).attr('fill', opts.fill)
245
- .attr('stroke', 'rgba(255,255,255,0.25)').attr('stroke-width', 1);
246
- if (opts.showInitials) {
247
- g.append('text').attr('text-anchor', 'middle').attr('dy', '0.35em')
248
- .attr('font-size', Math.max(6.5, opts.r * 0.72)).attr('font-weight', 700)
249
- .attr('fill', opts.textFill ?? 'rgba(8,10,25,0.92)')
250
- .attr('pointer-events', 'none').text(initialsFor(opts.name));
251
- }
252
- if (opts.imageUrl) {
253
- const clipId = `bridgering-clip-${String(opts.key).replace(/[^\w-]/g, '_')}`;
254
- defs.append('clipPath').attr('id', clipId).append('circle').attr('r', opts.r);
255
- g.append('image').attr('href', opts.imageUrl)
256
- .attr('x', -opts.r).attr('y', -opts.r)
257
- .attr('width', opts.r * 2).attr('height', opts.r * 2)
258
- .attr('clip-path', `url(#${clipId})`)
259
- .attr('preserveAspectRatio', 'xMidYMid slice')
260
- .attr('pointer-events', 'none')
261
- .on('error', (event) => { select(event.currentTarget).attr('display', 'none'); });
262
- }
263
- }
264
-
265
- // ---- static furniture -------------------------------------------------
266
- svg.append('circle').attr('cx', cx).attr('cy', cy).attr('r', R)
267
- .attr('fill', 'none').attr('stroke', 'rgba(255,255,255,0.06)')
268
- .attr('stroke-width', 1.5).attr('stroke-dasharray', '2 7');
269
-
270
- const centerLine = svg.append('line').attr('x1', cx).attr('y1', cy)
271
- .attr('stroke', 'rgba(255,255,255,0.15)').attr('stroke-width', 1);
272
-
273
- // ---- the selected bridge's circle, fanned from the top ---------------
274
- const clusterOuter = svg.append('g').attr('transform', `translate(${cx}, ${cy - R})`);
275
- const clusterInner = clusterOuter.append('g')
276
- .attr('transform', 'scale(0.88)').style('opacity', 0)
277
- .style('transition', 'opacity 260ms ease');
278
-
279
- let scaleRaf = null;
280
- let scaleTimeout = null;
281
- const setClusterScale = (s) => clusterInner.attr('transform', `scale(${s})`);
282
- function tweenClusterScale(from, to, ms) {
283
- if (scaleRaf !== null) cancelAnimationFrame(scaleRaf);
284
- if (scaleTimeout !== null) clearTimeout(scaleTimeout);
285
- const t0 = performance.now();
286
- const frame = (t) => {
287
- const p = Math.min(1, (t - t0) / ms);
288
- const e = 1 - Math.pow(1 - p, 3);
289
- setClusterScale(from + (to - from) * e);
290
- if (p < 1) scaleRaf = requestAnimationFrame(frame);
291
- else scaleRaf = null;
292
- };
293
- scaleRaf = requestAnimationFrame(frame);
294
- // No animation frames arrive in a hidden tab; land the final scale anyway
295
- // rather than leaving the circle stuck at its entry size.
296
- scaleTimeout = setTimeout(() => { setClusterScale(to); scaleTimeout = null; }, ms + 120);
297
- }
298
- function hideCluster() { clusterInner.style('opacity', 0); setClusterScale(0.92); }
299
- function showCluster() {
300
- clusterInner.style('opacity', 1);
301
- // Each node carries its own transition delay — flipping opacity here is
302
- // what produces the node-by-node "scan" reveal.
303
- clusterInner.selectAll('.cluster-node, .cluster-chip').style('opacity', 1);
304
- tweenClusterScale(0.9, 1, 300);
305
- }
306
-
307
- let suppressClick = false;
308
-
309
- const clusterAll = clusterMap.get(selected.id) ?? [];
310
- const clusterNodes = [...clusterAll]
311
- .sort((a, b) => (Number(b.power_score) || 0) - (Number(a.power_score) || 0))
312
- .slice(0, MAX_CLUSTER_NODES);
313
-
314
- if (clusterNodes.length > 0) {
315
- const bandCount = clusterNodes.length <= 6 ? 2 : 3;
316
- const spread = (100 * Math.PI) / 180;
317
- const perBand = Array.from({ length: bandCount }, () => []);
318
- clusterNodes.forEach((_, i) => perBand[i % bandCount].push(i));
319
- const placed = [];
320
- perBand.forEach((idxs, band) => {
321
- const rb = R + bandBase + band * bandGap;
322
- idxs.forEach((nodeIdx, j) => {
323
- const a = idxs.length === 1 ? TOP : TOP - spread / 2 + (spread * j) / (idxs.length - 1);
324
- const c = clusterNodes[nodeIdx];
325
- placed.push({
326
- c,
327
- x: rb * Math.cos(a),
328
- y: rb * Math.sin(a) + R,
329
- r: Math.max(5, Math.min(9, 5 + ((Number(c.power_score) || 0) / 13) * 4)),
330
- });
331
- });
332
- });
333
-
334
- // Deformable boundary: padded convex hull, smoothed and softly blurred.
335
- const padPoints = [];
336
- placed.forEach((p) => {
337
- for (let s = 0; s < 8; s++) {
338
- const a = (s / 8) * Math.PI * 2;
339
- padPoints.push({
340
- x: p.x + (p.r + HULL_PAD_PX) * Math.cos(a),
341
- y: p.y + (p.r + HULL_PAD_PX) * Math.sin(a),
342
- });
343
- }
344
- });
345
- const boundary = catmullRomClosed(convexHull(padPoints));
346
- const tierColor = TIER_COLORS[selected.tier] || TIER_COLORS.D;
347
-
348
- const cx0 = placed.reduce((s, p) => s + p.x, 0) / placed.length;
349
- const cy0 = placed.reduce((s, p) => s + p.y, 0) / placed.length;
350
- const nebulaR = Math.max(...placed.map((p) => Math.hypot(p.x - cx0, p.y - cy0))) + 40;
351
- clusterInner.append('circle').attr('cx', cx0).attr('cy', cy0).attr('r', nebulaR)
352
- .style('color', tierColor).attr('fill', 'url(#bridge-nebula)');
353
-
354
- if (boundary) {
355
- clusterInner.append('path').attr('d', boundary).attr('fill', tierColor)
356
- .attr('fill-opacity', 0.07).attr('filter', 'url(#bridge-cloud-blur)');
357
- clusterInner.append('path').attr('d', boundary).attr('fill', 'none')
358
- .attr('stroke', tierColor).attr('stroke-opacity', 0.2).attr('stroke-width', 1);
359
- }
360
-
361
- placed.forEach((p) => {
362
- clusterInner.append('line').attr('x1', 0).attr('y1', 0).attr('x2', p.x).attr('y2', p.y)
363
- .attr('stroke', 'rgba(255,255,255,0.06)').attr('stroke-width', 1);
364
- });
365
-
366
- placed.forEach((p, i) => {
367
- const g = clusterInner.append('g').attr('class', 'cluster-node')
368
- .attr('transform', `translate(${p.x}, ${p.y})`)
369
- .style('cursor', 'pointer').style('opacity', 0)
370
- .style('transition', `opacity 200ms ease ${i * 16}ms`);
371
- avatar(g, {
372
- key: `c-${p.c.id}`, r: p.r,
373
- fill: TIER_COLORS[p.c.tier] || TIER_COLORS.D,
374
- name: p.c.name, imageUrl: p.c.profile_image_url, showInitials: p.r >= 8,
375
- });
376
- g.append('title').text(p.c.headline ? `${p.c.name} — ${p.c.headline}` : p.c.name);
377
- g.on('click', (event) => {
378
- event.stopPropagation();
379
- if (suppressClick) { suppressClick = false; return; }
380
- cbRef.current.onSelect?.(p.c);
381
- });
382
- });
383
-
384
- const chipY = Math.min(...placed.map((p) => p.y - p.r)) - 22;
385
- const sCount = selected.circle_s_count ?? 0;
386
- const aCount = selected.circle_a_count ?? 0;
387
- // Say so when there is more than fits. A silent cap looks like a small
388
- // circle rather than a truncated one.
389
- const shown = placed.length < clusterAll.length
390
- ? `showing ${placed.length} of ${clusterAll.length}`
391
- : `${placed.length} people`;
392
- const label = selected.is_catalyst
393
- ? `⚡ catalyst circle · ${shown}`
394
- : `circle unlocked · ${shown} · ${sCount}S ${aCount}A`;
395
- const chip = clusterInner.append('g').attr('class', 'cluster-chip')
396
- .attr('transform', `translate(${cx0}, ${chipY})`)
397
- .style('opacity', 0)
398
- .style('transition', `opacity 240ms ease ${placed.length * 16 + 80}ms`);
399
- const chipText = chip.append('text').attr('text-anchor', 'middle').attr('dy', '0.35em')
400
- .attr('font-size', 11).attr('font-weight', 600)
401
- .attr('fill', selected.is_catalyst ? TIER_COLORS.S : '#e2e8f0')
402
- .text(label);
403
- const tw = (chipText.node()?.getComputedTextLength() ?? label.length * 6) + 20;
404
- chip.insert('rect', 'text')
405
- .attr('x', -tw / 2).attr('y', -11).attr('width', tw).attr('height', 22).attr('rx', 11)
406
- .attr('fill', 'rgba(10,10,25,0.75)')
407
- .attr('stroke', selected.is_catalyst ? TIER_COLORS.S : 'rgba(255,255,255,0.15)')
408
- .attr('stroke-opacity', selected.is_catalyst ? 0.6 : 1)
409
- .attr('stroke-width', 1);
410
- }
411
-
412
- // ---- bridge nodes on the dial ----------------------------------------
413
- const bridgesG = svg.append('g');
414
- const bridgeGroups = ring.map((b, i) => {
415
- // Power still shows through as size, but bounded by what the ring can hold.
416
- const r = labelsFit
417
- ? Math.min(nodeR, 14 + (Number(b.power_score) || 0))
418
- : nodeR + (i === selIndex ? 4 : 0);
419
- const isSel = i === selIndex;
420
- const g = bridgesG.append('g').style('cursor', 'pointer').attr('opacity', isSel ? 1 : 0.6);
421
- if (b.is_catalyst) {
422
- g.append('circle').attr('r', r + 4).attr('fill', 'none')
423
- .attr('stroke', TIER_COLORS.S).attr('stroke-opacity', 0.85).attr('stroke-width', 1.5);
424
- }
425
- if (isSel) {
426
- g.append('circle').attr('r', r + (b.is_catalyst ? 8 : 5)).attr('fill', 'none')
427
- .attr('stroke', 'rgba(255,255,255,0.35)').attr('stroke-width', 1);
428
- }
429
- avatar(g, {
430
- key: `b-${b.id}`, r, fill: TIER_COLORS[b.tier] || TIER_COLORS.D,
431
- name: b.name, imageUrl: b.profile_image_url, showInitials: true,
432
- });
433
- // When the ring is tight, only the selection is named — forty labels on a
434
- // circle is not a readable dial, it is a wall of text.
435
- if (!labelsFit && !isSel) return g;
436
- const labels = g.append('g')
437
- .attr('opacity', isSel ? 1 : isMobile ? 0.15 : 0.7)
438
- .attr('pointer-events', 'none');
439
- labels.append('text').attr('text-anchor', 'middle').attr('y', r + 15)
440
- .attr('font-size', 11).attr('font-weight', isSel ? 700 : 500)
441
- .attr('fill', isSel ? '#f8fafc' : '#94a3b8').text(shortName(b.name));
442
- labels.append('text').attr('text-anchor', 'middle').attr('y', r + 28)
443
- .attr('font-size', 9.5).attr('fill', TIER_COLORS[b.tier] || TIER_COLORS.D)
444
- .attr('fill-opacity', 0.9)
445
- .text(`${(Number(b.circle_power) || 0).toFixed(1)} circle power`);
446
- g.append('title').text(b.headline ? `${b.name} — ${b.headline}` : b.name);
447
- g.on('click', (event) => {
448
- event.stopPropagation();
449
- if (suppressClick) { suppressClick = false; return; }
450
- fadeHint();
451
- rotateToSlot(i);
452
- });
453
- return g;
454
- });
455
-
456
- // ---- you, fixed at the centre ----------------------------------------
457
- const centerG = svg.append('g').attr('transform', `translate(${cx}, ${cy})`);
458
- const uR = isMobile ? 22 : 28;
459
- centerG.append('circle').attr('r', uR + 6).attr('fill', 'none')
460
- .attr('stroke', 'rgba(255,255,255,0.08)').attr('stroke-width', 1);
461
- avatar(centerG, {
462
- key: 'user', r: uR, fill: '#1c1c42', textFill: '#e2e8f0',
463
- name: userName, imageUrl: userImage, showInitials: true,
464
- });
465
- centerG.append('text').attr('text-anchor', 'middle').attr('y', uR + 17)
466
- .attr('font-size', 11).attr('font-weight', 600).attr('fill', '#cbd5e1')
467
- .text(shortName(userName));
468
- centerG.append('text').attr('text-anchor', 'middle').attr('y', uR + 30)
469
- .attr('font-size', 9).attr('fill', '#64748b').text('you');
470
-
471
- // ---- affordance, fades after the first interaction --------------------
472
- // A visible pager.
473
- //
474
- // The dial holds twelve, sorted by circle power — which skews hard to your
475
- // strongest people, so lower tiers never appeared on the first page while
476
- // the tier filter beside it was counting every bridge. Chain view shows all
477
- // of them, so the two disagreed and only this one was lying. Shift+arrow
478
- // already paged, but nothing said so.
479
- if (total > MAX_BRIDGES) {
480
- const pagerY = Math.max(20, cy - R - (isMobile ? 96 : 118));
481
- const first = start + 1;
482
- const last = start + n; // n is the real page size, short on the last page
483
- const tiersHere = [...new Set(ring.map((b) => b.tier).filter(Boolean))]
484
- .sort((a, b) => 'SABCD'.indexOf(a) - 'SABCD'.indexOf(b));
485
-
486
- const pager = svg.append('g').attr('transform', `translate(${cx}, ${pagerY})`);
487
-
488
- pager.append('text')
489
- .attr('text-anchor', 'middle')
490
- .attr('font-size', 10.5)
491
- .attr('fill', 'rgba(255,255,255,0.4)')
492
- .attr('letter-spacing', '0.06em')
493
- .attr('pointer-events', 'none')
494
- .text(pageCount > 1
495
- ? `${first}\u2013${last} of ${total} \u00b7 page ${page + 1}/${pageCount} \u00b7 by circle power`
496
- : `all ${total} bridges \u00b7 by circle power`);
497
-
498
- // Which tiers you are actually looking at, so a page of all-S is obvious.
499
- pager.append('text')
500
- .attr('y', 15)
501
- .attr('text-anchor', 'middle')
502
- .attr('font-size', 10)
503
- .attr('pointer-events', 'none')
504
- .selectAll('tspan')
505
- .data(tiersHere)
506
- .join('tspan')
507
- .attr('dx', (d, i) => (i ? 7 : 0))
508
- .attr('fill', (d) => TIER_COLORS[d] || TIER_COLORS.D)
509
- .text((d) => d);
510
-
511
- const arrow = (dx, dir, glyph) => {
512
- const g = pager.append('g')
513
- .attr('transform', `translate(${dx}, 0)`)
514
- .style('cursor', 'pointer');
515
- g.append('circle').attr('r', 11).attr('fill', 'rgba(255,255,255,0.06)')
516
- .attr('stroke', 'rgba(255,255,255,0.14)').attr('stroke-width', 1);
517
- g.append('text').attr('text-anchor', 'middle').attr('dy', '0.34em')
518
- .attr('font-size', 12).attr('fill', '#cbd5e1')
519
- .attr('pointer-events', 'none').text(glyph);
520
- g.append('title').text(dir > 0 ? 'Next bridges' : 'Previous bridges');
521
- g.on('click', (event) => {
522
- event.stopPropagation();
523
- suppressClick = true;
524
- setRingStart((v) => v + dir);
525
- });
526
- };
527
- if (pageCount > 1) {
528
- arrow(-(isMobile ? 92 : 118), -1, '\u2039');
529
- arrow(isMobile ? 92 : 118, 1, '\u203a');
530
- }
531
- }
532
-
533
- const hint = interactedRef.current ? null : svg.append('text')
534
- .attr('x', cx)
535
- .attr('y', Math.min(isMobile ? h * 0.54 - 12 : h - 12, cy + R + (isMobile ? 42 : 54)))
536
- .attr('text-anchor', 'middle').attr('font-size', 11)
537
- .attr('fill', 'rgba(255,255,255,0.3)').attr('letter-spacing', '0.08em')
538
- .attr('pointer-events', 'none').style('transition', 'opacity 600ms ease')
539
- .text('drag to rotate · release to snap');
540
-
541
- function fadeHint() {
542
- if (interactedRef.current) return;
543
- interactedRef.current = true;
544
- hint?.style('opacity', 0);
545
- }
546
-
547
- // ---- rotation core ----------------------------------------------------
548
- function layout() {
549
- const off = offsetRef.current;
550
- bridgeGroups.forEach((g, i) => {
551
- const a = slot(i) + off;
552
- g.attr('transform', `translate(${cx + R * Math.cos(a)}, ${cy + R * Math.sin(a)})`);
553
- });
554
- const sa = slot(selIndex) + off;
555
- centerLine.attr('x2', cx + R * Math.cos(sa)).attr('y2', cy + R * Math.sin(sa));
556
- }
557
-
558
- let tweenTimeout = null;
559
-
560
- function cancelTween() {
561
- if (tweenRef.current !== null) {
562
- cancelAnimationFrame(tweenRef.current);
563
- tweenRef.current = null;
564
- }
565
- if (tweenTimeout !== null) {
566
- clearTimeout(tweenTimeout);
567
- tweenTimeout = null;
568
- }
569
- }
570
-
571
- function tweenTo(target, done) {
572
- cancelTween();
573
- const from = offsetRef.current;
574
- const delta = normalizeAngle(target - from);
575
- const t0 = performance.now();
576
- let settled = false;
577
- const finish = () => {
578
- if (settled) return;
579
- settled = true;
580
- cancelTween();
581
- offsetRef.current = from + delta;
582
- layout();
583
- done?.();
584
- };
585
- const frame = (t) => {
586
- const p = Math.min(1, (t - t0) / SNAP_MS);
587
- const e = 1 - Math.pow(1 - p, 3);
588
- offsetRef.current = from + delta * e;
589
- layout();
590
- if (p < 1) tweenRef.current = requestAnimationFrame(frame);
591
- else { tweenRef.current = null; finish(); }
592
- };
593
- tweenRef.current = requestAnimationFrame(frame);
594
- // The animation rides on requestAnimationFrame, which does not run in a
595
- // hidden or background tab — and the SELECTION is committed in the
596
- // callback. Without this, clicking a bridge in a background tab changes
597
- // nothing at all. Land the state even when no frame ever arrives.
598
- tweenTimeout = setTimeout(finish, SNAP_MS + 120);
599
- }
600
-
601
- function rotateToSlot(i) {
602
- const target = offsetRef.current + normalizeAngle(TOP - slot(i) - offsetRef.current);
603
- const bridge = ring[i];
604
- const id = bridge.id;
605
- if (id !== selectedId) hideCluster();
606
- tweenTo(target, () => {
607
- // The dial owns which bridge is at the top; the rest of the app owns
608
- // showing who that is. Before this, clicking a bridge rotated it and
609
- // told nobody, which reads exactly like the click doing nothing.
610
- cbRef.current.onSelect?.(bridge);
611
- if (id !== selectedId) setSelectedId(id);
612
- else showCluster();
613
- });
614
- }
615
-
616
- // ---- pointer handling (mouse + touch) ---------------------------------
617
- let dragging = false;
618
- let moved = false;
619
- let startX = 0;
620
- let startY = 0;
621
- let lastPointerAngle = 0;
622
-
623
- const svgPoint = (e) => {
624
- const rect = svgEl.getBoundingClientRect();
625
- return {
626
- x: rect.width > 0 ? ((e.clientX - rect.left) / rect.width) * w : 0,
627
- y: rect.height > 0 ? ((e.clientY - rect.top) / rect.height) * h : 0,
628
- };
629
- };
630
-
631
- function onPointerDown(e) {
632
- if (e.pointerType === 'mouse' && e.button !== 0) return;
633
- cancelTween();
634
- dragging = true;
635
- moved = false;
636
- suppressClick = false;
637
- startX = e.clientX;
638
- startY = e.clientY;
639
- const p = svgPoint(e);
640
- lastPointerAngle = Math.atan2(p.y - cy, p.x - cx);
641
- try { svgEl.setPointerCapture(e.pointerId); } catch { /* best effort */ }
642
- }
643
-
644
- function onPointerMove(e) {
645
- if (!dragging) return;
646
- if (!moved) {
647
- if (Math.hypot(e.clientX - startX, e.clientY - startY) < CLICK_SLOP_PX) return;
648
- moved = true;
649
- suppressClick = true;
650
- svgEl.style.cursor = 'grabbing';
651
- hideCluster();
652
- fadeHint();
653
- }
654
- const p = svgPoint(e);
655
- const ang = Math.atan2(p.y - cy, p.x - cx);
656
- offsetRef.current += normalizeAngle(ang - lastPointerAngle);
657
- lastPointerAngle = ang;
658
- layout(); // 1:1 while the pointer is down; snapping happens on release
659
- }
660
-
661
- function onPointerUp(e) {
662
- if (!dragging) return;
663
- dragging = false;
664
- svgEl.style.cursor = 'grab';
665
- try { svgEl.releasePointerCapture(e.pointerId); } catch { /* already released */ }
666
- if (!moved) return;
667
- let best = 0;
668
- let bestDist = Infinity;
669
- for (let i = 0; i < n; i++) {
670
- const d = Math.abs(normalizeAngle(slot(i) + offsetRef.current - TOP));
671
- if (d < bestDist) { bestDist = d; best = i; }
672
- }
673
- rotateToSlot(best);
674
- }
675
-
676
- function onSvgClick() {
677
- if (suppressClick) { suppressClick = false; return; }
678
- cbRef.current.onSelect?.(null);
679
- }
680
-
681
- // The dial must be operable without a pointer.
682
- function onKeyDown(e) {
683
- let delta = 0;
684
- if (e.key === 'ArrowRight' || e.key === 'ArrowDown') delta = 1;
685
- else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') delta = -1;
686
- else return;
687
- e.preventDefault();
688
- fadeHint();
689
- // Shift pages the window through the full list; plain arrows move within
690
- // the twelve currently on the dial.
691
- if (e.shiftKey && pageCount > 1) {
692
- setRingStart((v) => v + delta);
693
- return;
694
- }
695
- rotateToSlot((selIndex + delta + n) % n);
696
- }
697
-
698
- svgEl.addEventListener('pointerdown', onPointerDown);
699
- svgEl.addEventListener('pointermove', onPointerMove);
700
- svgEl.addEventListener('pointerup', onPointerUp);
701
- svgEl.addEventListener('pointercancel', onPointerUp);
702
- svgEl.addEventListener('click', onSvgClick);
703
- svgEl.addEventListener('keydown', onKeyDown);
704
-
705
- // Canonical offset puts the selected bridge exactly at the top slot.
706
- offsetRef.current = normalizeAngle(-selIndex * step);
707
- layout();
708
-
709
- // Double rAF so the hidden state paints before the transition begins —
710
- // with a timeout behind it, because a background tab gets no frames and the
711
- // circle would simply never appear.
712
- const fadeRafs = [];
713
- let revealed = false;
714
- const reveal = () => { if (revealed) return; revealed = true; showCluster(); };
715
- fadeRafs.push(requestAnimationFrame(() => {
716
- fadeRafs.push(requestAnimationFrame(reveal));
717
- }));
718
- const revealTimeout = setTimeout(reveal, 200);
719
-
720
- return () => {
721
- cancelTween();
722
- if (scaleRaf !== null) cancelAnimationFrame(scaleRaf);
723
- if (scaleTimeout !== null) clearTimeout(scaleTimeout);
724
- clearTimeout(revealTimeout);
725
- fadeRafs.forEach((id) => cancelAnimationFrame(id));
726
- svgEl.removeEventListener('pointerdown', onPointerDown);
727
- svgEl.removeEventListener('pointermove', onPointerMove);
728
- svgEl.removeEventListener('pointerup', onPointerUp);
729
- svgEl.removeEventListener('pointercancel', onPointerUp);
730
- svgEl.removeEventListener('click', onSvgClick);
731
- svgEl.removeEventListener('keydown', onKeyDown);
732
- svg.selectAll('*').remove();
733
- };
734
- }, [signature, selectedId, ringStart, isMobile, size, userName, userImage]);
735
-
736
- return (
737
- <div
738
- ref={containerRef}
739
- // The view area is a flex column, so a percentage height resolves
740
- // against nothing and collapses to zero. Flex into the space instead,
741
- // the way the other views do.
742
- style={{ position: 'relative', flex: 1, minHeight: 0, width: '100%' }}
743
- >
744
- <svg
745
- ref={svgRef}
746
- tabIndex={0}
747
- role="application"
748
- aria-label="Bridge dial. Drag to rotate, or use the arrow keys to select the previous or next bridge; the selected bridge sits at the top with their known circle fanned above."
749
- style={{ height: '100%', width: '100%', userSelect: 'none', touchAction: 'none', borderRadius: 12, outline: 'none' }}
750
- />
751
- </div>
752
- );
753
- }