@wopr-network/platform-ui-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (543) hide show
  1. package/.env.paperclip +18 -0
  2. package/.env.wopr +18 -0
  3. package/README.md +36 -0
  4. package/biome.json +52 -0
  5. package/next.config.ts +45 -0
  6. package/package.json +84 -0
  7. package/postcss.config.mjs +7 -0
  8. package/public/file.svg +1 -0
  9. package/public/globe.svg +1 -0
  10. package/public/window.svg +1 -0
  11. package/src/__tests__/__snapshots__/layout-snapshots.test.tsx.snap +741 -0
  12. package/src/__tests__/account-page-redirect.test.tsx +73 -0
  13. package/src/__tests__/account-switcher.test.tsx +85 -0
  14. package/src/__tests__/activity-page.test.tsx +176 -0
  15. package/src/__tests__/add-payment-method-dialog.test.tsx +160 -0
  16. package/src/__tests__/admin-api.test.ts +244 -0
  17. package/src/__tests__/admin-gpu-api.test.ts +188 -0
  18. package/src/__tests__/admin-guard.test.tsx +79 -0
  19. package/src/__tests__/admin-marketplace-api.test.ts +179 -0
  20. package/src/__tests__/admin-middleware.test.ts +157 -0
  21. package/src/__tests__/admin-tenant-table.test.tsx +95 -0
  22. package/src/__tests__/affiliate-dashboard.test.tsx +178 -0
  23. package/src/__tests__/api-401-redirect.test.ts +78 -0
  24. package/src/__tests__/api-client.test.ts +316 -0
  25. package/src/__tests__/api-config.test.ts +89 -0
  26. package/src/__tests__/api-control-instance.test.ts +69 -0
  27. package/src/__tests__/api-fleet-resources.test.ts +52 -0
  28. package/src/__tests__/api-fleet-trpc.test.ts +252 -0
  29. package/src/__tests__/api-get-instance-config.test.ts +41 -0
  30. package/src/__tests__/api-null-guards.test.ts +244 -0
  31. package/src/__tests__/api-rename-instance.test.ts +60 -0
  32. package/src/__tests__/api-update-instance-config.test.ts +60 -0
  33. package/src/__tests__/audit-log-table-pagination.test.tsx +136 -0
  34. package/src/__tests__/auth-client.test.ts +87 -0
  35. package/src/__tests__/auth-password-reset.test.tsx +435 -0
  36. package/src/__tests__/auth-redirect.test.tsx +60 -0
  37. package/src/__tests__/auth.test.tsx +269 -0
  38. package/src/__tests__/auto-topup-card.test.tsx +257 -0
  39. package/src/__tests__/backups-tab.test.tsx +221 -0
  40. package/src/__tests__/billing-byok-callout.test.tsx +76 -0
  41. package/src/__tests__/billing-layout-nav-hidden.test.tsx +47 -0
  42. package/src/__tests__/billing-payment-org-invoices.test.tsx +123 -0
  43. package/src/__tests__/billing.test.tsx +509 -0
  44. package/src/__tests__/bot-settings/resources-tab.test.tsx +119 -0
  45. package/src/__tests__/bot-settings/storage-tab.test.tsx +80 -0
  46. package/src/__tests__/bot-settings/vps-info-panel.test.tsx +108 -0
  47. package/src/__tests__/bot-settings/vps-upgrade-card.test.tsx +52 -0
  48. package/src/__tests__/bot-settings-data-control.test.ts +49 -0
  49. package/src/__tests__/bot-settings-restart.test.tsx +149 -0
  50. package/src/__tests__/bot-settings.test.tsx +678 -0
  51. package/src/__tests__/brand.test.ts +335 -0
  52. package/src/__tests__/buy-credits-panel.test.tsx +249 -0
  53. package/src/__tests__/buy-crypto-credits-panel.test.tsx +178 -0
  54. package/src/__tests__/capability-conflicts.test.ts +88 -0
  55. package/src/__tests__/capability-resolver.test.tsx +173 -0
  56. package/src/__tests__/changeset-detail.test.tsx +156 -0
  57. package/src/__tests__/channel-setup-logger.test.ts +22 -0
  58. package/src/__tests__/channel-setup-toast.test.tsx +60 -0
  59. package/src/__tests__/channel-wizard.test.tsx +505 -0
  60. package/src/__tests__/chat/ambient-dot.test.tsx +35 -0
  61. package/src/__tests__/chat/chat-input.test.tsx +78 -0
  62. package/src/__tests__/chat/chat-message.test.tsx +45 -0
  63. package/src/__tests__/chat/chat-panel.test.tsx +111 -0
  64. package/src/__tests__/chat/chat-widget.test.tsx +82 -0
  65. package/src/__tests__/chat-store.test.ts +87 -0
  66. package/src/__tests__/command-center.test.tsx +246 -0
  67. package/src/__tests__/compliance-retention-edit.test.tsx +134 -0
  68. package/src/__tests__/coupon-input.test.tsx +119 -0
  69. package/src/__tests__/create-instance.test.tsx +96 -0
  70. package/src/__tests__/create-org-wizard.test.tsx +200 -0
  71. package/src/__tests__/credit-balance.test.tsx +103 -0
  72. package/src/__tests__/credits.test.tsx +376 -0
  73. package/src/__tests__/csrf-middleware.test.ts +198 -0
  74. package/src/__tests__/degraded-state-banner.test.tsx +130 -0
  75. package/src/__tests__/dividend-calculator.test.tsx +20 -0
  76. package/src/__tests__/dividend-stats.test.tsx +64 -0
  77. package/src/__tests__/dividend.test.tsx +169 -0
  78. package/src/__tests__/dockerfile.test.ts +110 -0
  79. package/src/__tests__/email-verification-banner.test.tsx +64 -0
  80. package/src/__tests__/env-example.test.ts +25 -0
  81. package/src/__tests__/error-boundaries.test.tsx +64 -0
  82. package/src/__tests__/fetch-pricing.test.ts +121 -0
  83. package/src/__tests__/field-oauth.test.tsx +302 -0
  84. package/src/__tests__/fixtures/mock-manifests-data.js +372 -0
  85. package/src/__tests__/fixtures/mock-manifests.ts +24 -0
  86. package/src/__tests__/fleet-health-timestamp.test.tsx +101 -0
  87. package/src/__tests__/fleet-health-update.test.tsx +83 -0
  88. package/src/__tests__/format-credit.test.ts +58 -0
  89. package/src/__tests__/gpu-dashboard.test.tsx +236 -0
  90. package/src/__tests__/hosted-usage-date-range.test.tsx +54 -0
  91. package/src/__tests__/instance-detail.test.tsx +571 -0
  92. package/src/__tests__/instance-list.test.tsx +230 -0
  93. package/src/__tests__/landing-hero.test.tsx +27 -0
  94. package/src/__tests__/landing-nav.test.tsx +24 -0
  95. package/src/__tests__/layout-snapshots.test.tsx +167 -0
  96. package/src/__tests__/logger.test.ts +54 -0
  97. package/src/__tests__/login-page-redirect.test.tsx +142 -0
  98. package/src/__tests__/manifest-validation.test.ts +126 -0
  99. package/src/__tests__/marketplace-admin.test.tsx +151 -0
  100. package/src/__tests__/marketplace.test.tsx +609 -0
  101. package/src/__tests__/merge-api-rates.test.ts +70 -0
  102. package/src/__tests__/middleware.test.ts +690 -0
  103. package/src/__tests__/network-page.test.tsx +100 -0
  104. package/src/__tests__/next-config-headers.test.ts +28 -0
  105. package/src/__tests__/not-found.test.tsx +26 -0
  106. package/src/__tests__/notifications.test.tsx +128 -0
  107. package/src/__tests__/oauth-buttons.test.tsx +101 -0
  108. package/src/__tests__/oauth-error-mapping.test.tsx +97 -0
  109. package/src/__tests__/observability.test.tsx +541 -0
  110. package/src/__tests__/onboarding-data.test.ts +363 -0
  111. package/src/__tests__/onboarding-page.test.tsx +113 -0
  112. package/src/__tests__/onboarding-store.test.ts +121 -0
  113. package/src/__tests__/org-billing-api.test.tsx +70 -0
  114. package/src/__tests__/org-billing-null-guards.test.ts +64 -0
  115. package/src/__tests__/org-billing-page.test.tsx +124 -0
  116. package/src/__tests__/plugin-definition.test.ts +43 -0
  117. package/src/__tests__/plugin-install-flow.test.tsx +535 -0
  118. package/src/__tests__/plugin-registry.test.tsx +475 -0
  119. package/src/__tests__/plugin-setup/setup-chat-panel.test.ts +142 -0
  120. package/src/__tests__/plugin-setup/use-plugin-setup-chat.test.ts +49 -0
  121. package/src/__tests__/plugin-tool-definitions.test.ts +51 -0
  122. package/src/__tests__/plugin-tool-sync.test.ts +59 -0
  123. package/src/__tests__/portfolio-chart.test.tsx +24 -0
  124. package/src/__tests__/pricing.test.tsx +107 -0
  125. package/src/__tests__/promotion-form.test.tsx +180 -0
  126. package/src/__tests__/promotions-list.test.tsx +194 -0
  127. package/src/__tests__/provider-key-api.test.ts +134 -0
  128. package/src/__tests__/resend-verification-button.test.tsx +104 -0
  129. package/src/__tests__/sanitize-redirect-url.test.ts +47 -0
  130. package/src/__tests__/secrets-audit-pagination.test.tsx +139 -0
  131. package/src/__tests__/settings.test.tsx +937 -0
  132. package/src/__tests__/setup-checklist.test.tsx +274 -0
  133. package/src/__tests__/setup.ts +82 -0
  134. package/src/__tests__/smoke.test.tsx +10 -0
  135. package/src/__tests__/snapshot-api.test.ts +104 -0
  136. package/src/__tests__/status-api.test.ts +46 -0
  137. package/src/__tests__/status-badge.test.tsx +33 -0
  138. package/src/__tests__/status-colors.test.ts +83 -0
  139. package/src/__tests__/status-page.test.tsx +86 -0
  140. package/src/__tests__/step-superpowers.test.tsx +218 -0
  141. package/src/__tests__/story-sections.test.tsx +24 -0
  142. package/src/__tests__/superpower-content-sanitize.test.tsx +87 -0
  143. package/src/__tests__/superpower-content.test.tsx +44 -0
  144. package/src/__tests__/suspension-banner.test.tsx +140 -0
  145. package/src/__tests__/tenant-context.test.tsx +146 -0
  146. package/src/__tests__/tenant-keys-api.test.ts +114 -0
  147. package/src/__tests__/tenant-table-pagination.test.tsx +124 -0
  148. package/src/__tests__/terminal-log-cleanup.test.tsx +51 -0
  149. package/src/__tests__/terminal-sequence.test.tsx +28 -0
  150. package/src/__tests__/transaction-history.test.tsx +325 -0
  151. package/src/__tests__/trpc-types.test.ts +102 -0
  152. package/src/__tests__/use-capability-meta.test.ts +161 -0
  153. package/src/__tests__/use-chat.test.ts +616 -0
  154. package/src/__tests__/use-has-org.test.ts +44 -0
  155. package/src/__tests__/use-image-status.test.ts +77 -0
  156. package/src/__tests__/use-pagination-params.test.ts +88 -0
  157. package/src/__tests__/use-plugin-setup-chat-stale-closure.test.ts +53 -0
  158. package/src/__tests__/use-webmcp.test.ts +119 -0
  159. package/src/__tests__/validate-elevenlabs-key.test.ts +95 -0
  160. package/src/__tests__/validate-redirect-url.test.ts +61 -0
  161. package/src/__tests__/verify-page.test.tsx +140 -0
  162. package/src/__tests__/verify-redirect.test.tsx +41 -0
  163. package/src/__tests__/verify-result-banner.test.tsx +66 -0
  164. package/src/__tests__/webmcp-feature-detect.test.ts +54 -0
  165. package/src/__tests__/webmcp-hook.test.tsx +72 -0
  166. package/src/__tests__/webmcp-marketplace-onboarding-tools.test.ts +185 -0
  167. package/src/__tests__/webmcp-register.test.ts +103 -0
  168. package/src/__tests__/webmcp-set-provider.test.ts +47 -0
  169. package/src/__tests__/webmcp-tools.test.ts +348 -0
  170. package/src/app/(auth)/error.tsx +72 -0
  171. package/src/app/(auth)/forgot-password/page.tsx +137 -0
  172. package/src/app/(auth)/layout.tsx +14 -0
  173. package/src/app/(auth)/loading.tsx +26 -0
  174. package/src/app/(auth)/login/page.tsx +188 -0
  175. package/src/app/(auth)/reset-password/page.tsx +169 -0
  176. package/src/app/(auth)/signup/page.tsx +309 -0
  177. package/src/app/(dashboard)/billing/credits/page.tsx +209 -0
  178. package/src/app/(dashboard)/billing/error.tsx +72 -0
  179. package/src/app/(dashboard)/billing/layout.tsx +73 -0
  180. package/src/app/(dashboard)/billing/loading.tsx +41 -0
  181. package/src/app/(dashboard)/billing/payment/page.tsx +639 -0
  182. package/src/app/(dashboard)/billing/plans/page.tsx +58 -0
  183. package/src/app/(dashboard)/billing/referrals/page.tsx +7 -0
  184. package/src/app/(dashboard)/billing/usage/hosted/page.tsx +348 -0
  185. package/src/app/(dashboard)/billing/usage/page.tsx +663 -0
  186. package/src/app/(dashboard)/changesets/[id]/changeset-detail-client.tsx +400 -0
  187. package/src/app/(dashboard)/changesets/[id]/error.tsx +57 -0
  188. package/src/app/(dashboard)/changesets/[id]/loading.tsx +23 -0
  189. package/src/app/(dashboard)/changesets/[id]/page.tsx +10 -0
  190. package/src/app/(dashboard)/changesets/error.tsx +72 -0
  191. package/src/app/(dashboard)/changesets/page.tsx +10 -0
  192. package/src/app/(dashboard)/chat/page.tsx +74 -0
  193. package/src/app/(dashboard)/dashboard/bots/[id]/settings/page.tsx +10 -0
  194. package/src/app/(dashboard)/dashboard/network/page.tsx +97 -0
  195. package/src/app/(dashboard)/dashboard/page.tsx +13 -0
  196. package/src/app/(dashboard)/error.tsx +72 -0
  197. package/src/app/(dashboard)/layout.tsx +113 -0
  198. package/src/app/(dashboard)/loading.tsx +27 -0
  199. package/src/app/(dashboard)/marketplace/[plugin]/page.tsx +548 -0
  200. package/src/app/(dashboard)/marketplace/error.tsx +72 -0
  201. package/src/app/(dashboard)/marketplace/loading.tsx +27 -0
  202. package/src/app/(dashboard)/marketplace/page.tsx +268 -0
  203. package/src/app/(dashboard)/not-found.tsx +46 -0
  204. package/src/app/(dashboard)/onboarding/page.tsx +267 -0
  205. package/src/app/(dashboard)/settings/account/page.tsx +132 -0
  206. package/src/app/(dashboard)/settings/activity/page.tsx +280 -0
  207. package/src/app/(dashboard)/settings/api-keys/page.tsx +530 -0
  208. package/src/app/(dashboard)/settings/brain/page.tsx +412 -0
  209. package/src/app/(dashboard)/settings/error.tsx +72 -0
  210. package/src/app/(dashboard)/settings/layout.tsx +114 -0
  211. package/src/app/(dashboard)/settings/loading.tsx +31 -0
  212. package/src/app/(dashboard)/settings/notifications/page.tsx +216 -0
  213. package/src/app/(dashboard)/settings/org/page.tsx +617 -0
  214. package/src/app/(dashboard)/settings/profile/page.tsx +510 -0
  215. package/src/app/(dashboard)/settings/providers/page.tsx +842 -0
  216. package/src/app/(dashboard)/settings/secrets/page.tsx +658 -0
  217. package/src/app/(dashboard)/settings/security/page.tsx +1133 -0
  218. package/src/app/admin/accounting/loading.tsx +32 -0
  219. package/src/app/admin/accounting/page.tsx +5 -0
  220. package/src/app/admin/affiliates/loading.tsx +32 -0
  221. package/src/app/admin/affiliates/page.tsx +5 -0
  222. package/src/app/admin/audit/loading.tsx +32 -0
  223. package/src/app/admin/audit/page.tsx +5 -0
  224. package/src/app/admin/billing-health/loading.tsx +17 -0
  225. package/src/app/admin/billing-health/page.tsx +10 -0
  226. package/src/app/admin/compliance/page.tsx +5 -0
  227. package/src/app/admin/error.tsx +72 -0
  228. package/src/app/admin/gpu/loading.tsx +38 -0
  229. package/src/app/admin/gpu/page.tsx +5 -0
  230. package/src/app/admin/incidents/page.tsx +10 -0
  231. package/src/app/admin/inference/loading.tsx +32 -0
  232. package/src/app/admin/inference/page.tsx +5 -0
  233. package/src/app/admin/layout.tsx +44 -0
  234. package/src/app/admin/loading.tsx +32 -0
  235. package/src/app/admin/marketplace/loading.tsx +32 -0
  236. package/src/app/admin/marketplace/page.tsx +5 -0
  237. package/src/app/admin/migrations/loading.tsx +22 -0
  238. package/src/app/admin/migrations/page.tsx +5 -0
  239. package/src/app/admin/onboarding/loading.tsx +18 -0
  240. package/src/app/admin/onboarding/page.tsx +5 -0
  241. package/src/app/admin/promotions/[id]/edit/loading.tsx +16 -0
  242. package/src/app/admin/promotions/[id]/edit/page.tsx +56 -0
  243. package/src/app/admin/promotions/[id]/loading.tsx +15 -0
  244. package/src/app/admin/promotions/[id]/page.tsx +311 -0
  245. package/src/app/admin/promotions/loading.tsx +21 -0
  246. package/src/app/admin/promotions/new/loading.tsx +16 -0
  247. package/src/app/admin/promotions/new/page.tsx +12 -0
  248. package/src/app/admin/promotions/page.tsx +266 -0
  249. package/src/app/admin/rate-overrides/loading.tsx +17 -0
  250. package/src/app/admin/rate-overrides/page.tsx +290 -0
  251. package/src/app/admin/roles/loading.tsx +27 -0
  252. package/src/app/admin/roles/page.tsx +5 -0
  253. package/src/app/admin/tenants/loading.tsx +32 -0
  254. package/src/app/admin/tenants/page.tsx +5 -0
  255. package/src/app/apple-icon.tsx +32 -0
  256. package/src/app/auth/callback/[provider]/page.tsx +104 -0
  257. package/src/app/auth/verify/page.tsx +224 -0
  258. package/src/app/channels/error.tsx +72 -0
  259. package/src/app/channels/loading.tsx +29 -0
  260. package/src/app/channels/page.tsx +262 -0
  261. package/src/app/channels/setup/[plugin]/page.tsx +136 -0
  262. package/src/app/error.tsx +72 -0
  263. package/src/app/favicon.ico +0 -0
  264. package/src/app/fleet/error.tsx +72 -0
  265. package/src/app/fleet/health/page.tsx +9 -0
  266. package/src/app/fleet/layout.tsx +14 -0
  267. package/src/app/fleet/loading.tsx +33 -0
  268. package/src/app/fleet/page.tsx +5 -0
  269. package/src/app/global-error.tsx +96 -0
  270. package/src/app/globals.css +251 -0
  271. package/src/app/icon.svg +4 -0
  272. package/src/app/instances/[id]/instance-detail-client.tsx +1298 -0
  273. package/src/app/instances/[id]/page.tsx +10 -0
  274. package/src/app/instances/error.tsx +72 -0
  275. package/src/app/instances/instance-list-client.tsx +540 -0
  276. package/src/app/instances/loading.tsx +33 -0
  277. package/src/app/instances/new/create-instance-client.tsx +377 -0
  278. package/src/app/instances/new/page.tsx +9 -0
  279. package/src/app/instances/page.tsx +9 -0
  280. package/src/app/layout.tsx +83 -0
  281. package/src/app/not-found.tsx +38 -0
  282. package/src/app/og/route.tsx +50 -0
  283. package/src/app/page.tsx +39 -0
  284. package/src/app/plugins/error.tsx +72 -0
  285. package/src/app/plugins/layout.tsx +14 -0
  286. package/src/app/plugins/loading.tsx +30 -0
  287. package/src/app/plugins/page.tsx +555 -0
  288. package/src/app/pricing/error.tsx +72 -0
  289. package/src/app/pricing/loading.tsx +25 -0
  290. package/src/app/pricing/page.tsx +20 -0
  291. package/src/app/privacy/page.tsx +406 -0
  292. package/src/app/robots.ts +9 -0
  293. package/src/app/sitemap.ts +11 -0
  294. package/src/app/status/error.tsx +72 -0
  295. package/src/app/status/loading.tsx +21 -0
  296. package/src/app/status/page.tsx +20 -0
  297. package/src/app/terms/page.tsx +414 -0
  298. package/src/components/account-switcher.tsx +82 -0
  299. package/src/components/admin/accounting-dashboard.tsx +190 -0
  300. package/src/components/admin/admin-guard.tsx +36 -0
  301. package/src/components/admin/admin-nav.tsx +71 -0
  302. package/src/components/admin/affiliate-dashboard.tsx +564 -0
  303. package/src/components/admin/audit-log-table.tsx +336 -0
  304. package/src/components/admin/billing-health-dashboard.test.tsx +40 -0
  305. package/src/components/admin/billing-health-dashboard.tsx +416 -0
  306. package/src/components/admin/bulk-actions-bar.test.tsx +92 -0
  307. package/src/components/admin/bulk-actions-bar.tsx +80 -0
  308. package/src/components/admin/bulk-export-dialog.test.tsx +75 -0
  309. package/src/components/admin/bulk-export-dialog.tsx +189 -0
  310. package/src/components/admin/bulk-grant-dialog.test.tsx +81 -0
  311. package/src/components/admin/bulk-grant-dialog.tsx +147 -0
  312. package/src/components/admin/bulk-preview-dialog.test.tsx +72 -0
  313. package/src/components/admin/bulk-preview-dialog.tsx +106 -0
  314. package/src/components/admin/bulk-reactivate-dialog.test.tsx +51 -0
  315. package/src/components/admin/bulk-reactivate-dialog.tsx +55 -0
  316. package/src/components/admin/bulk-select-all-banner.test.tsx +36 -0
  317. package/src/components/admin/bulk-select-all-banner.tsx +44 -0
  318. package/src/components/admin/bulk-suspend-dialog.test.tsx +77 -0
  319. package/src/components/admin/bulk-suspend-dialog.tsx +129 -0
  320. package/src/components/admin/bulk-undo-toast.test.tsx +66 -0
  321. package/src/components/admin/bulk-undo-toast.tsx +121 -0
  322. package/src/components/admin/compliance-dashboard.tsx +1341 -0
  323. package/src/components/admin/gpu-dashboard.tsx +552 -0
  324. package/src/components/admin/grant-credits-dialog.tsx +121 -0
  325. package/src/components/admin/incident-dashboard.test.tsx +44 -0
  326. package/src/components/admin/incident-dashboard.tsx +717 -0
  327. package/src/components/admin/inference-dashboard.tsx +415 -0
  328. package/src/components/admin/marketplace-admin.tsx +765 -0
  329. package/src/components/admin/migrations-dashboard.tsx +404 -0
  330. package/src/components/admin/onboarding-dashboard.tsx +422 -0
  331. package/src/components/admin/promotions/promotion-form.tsx +440 -0
  332. package/src/components/admin/roles-dashboard.tsx +278 -0
  333. package/src/components/admin/suspend-dialog.tsx +98 -0
  334. package/src/components/admin/tenant-notes-panel.tsx +134 -0
  335. package/src/components/admin/tenant-row-actions.tsx +78 -0
  336. package/src/components/admin/tenant-table.tsx +339 -0
  337. package/src/components/auth/auth-error.tsx +22 -0
  338. package/src/components/auth/auth-redirect.tsx +18 -0
  339. package/src/components/auth/auth-shell.tsx +25 -0
  340. package/src/components/auth/email-verification-banner.tsx +25 -0
  341. package/src/components/auth/email-verification-result-banner.tsx +70 -0
  342. package/src/components/auth/resend-verification-button.tsx +94 -0
  343. package/src/components/auth/wopr-wordmark.tsx +19 -0
  344. package/src/components/billing/add-payment-method-dialog.tsx +267 -0
  345. package/src/components/billing/affiliate-dashboard.tsx +300 -0
  346. package/src/components/billing/auto-topup-card.tsx +432 -0
  347. package/src/components/billing/buy-credits-panel.tsx +180 -0
  348. package/src/components/billing/buy-crypto-credits-panel.tsx +96 -0
  349. package/src/components/billing/byok-callout.tsx +87 -0
  350. package/src/components/billing/coupon-input.tsx +86 -0
  351. package/src/components/billing/credit-balance.tsx +79 -0
  352. package/src/components/billing/degraded-state-banner.tsx +95 -0
  353. package/src/components/billing/dividend-banner.tsx +97 -0
  354. package/src/components/billing/dividend-eligibility.tsx +86 -0
  355. package/src/components/billing/dividend-pool-stats.tsx +86 -0
  356. package/src/components/billing/first-dividend-dialog.tsx +109 -0
  357. package/src/components/billing/low-balance-banner.tsx +50 -0
  358. package/src/components/billing/org-billing-page.tsx +360 -0
  359. package/src/components/billing/suspension-banner.tsx +53 -0
  360. package/src/components/billing/transaction-history.tsx +239 -0
  361. package/src/components/bot-settings/__tests__/bot-settings-client.test.tsx +205 -0
  362. package/src/components/bot-settings/backups-tab.tsx +377 -0
  363. package/src/components/bot-settings/bot-settings-client.tsx +1712 -0
  364. package/src/components/bot-settings/resources-tab.tsx +203 -0
  365. package/src/components/bot-settings/storage-tab.tsx +248 -0
  366. package/src/components/bot-settings/vps-info-panel.tsx +132 -0
  367. package/src/components/bot-settings/vps-upgrade-card.tsx +110 -0
  368. package/src/components/capability/CapabilityResolver.tsx +113 -0
  369. package/src/components/channel-wizard/field-interactive.tsx +48 -0
  370. package/src/components/channel-wizard/field-oauth.tsx +181 -0
  371. package/src/components/channel-wizard/field-paste.tsx +47 -0
  372. package/src/components/channel-wizard/field-qr.tsx +302 -0
  373. package/src/components/channel-wizard/index.ts +6 -0
  374. package/src/components/channel-wizard/step-renderer.tsx +103 -0
  375. package/src/components/channel-wizard/wizard.tsx +200 -0
  376. package/src/components/chat/ambient-dot.tsx +32 -0
  377. package/src/components/chat/chat-input.tsx +56 -0
  378. package/src/components/chat/chat-message.tsx +36 -0
  379. package/src/components/chat/chat-panel.tsx +138 -0
  380. package/src/components/chat/chat-widget.tsx +41 -0
  381. package/src/components/chat/index.ts +5 -0
  382. package/src/components/dashboard/command-center.tsx +614 -0
  383. package/src/components/instances/friends-tab.test.tsx +265 -0
  384. package/src/components/instances/friends-tab.tsx +721 -0
  385. package/src/components/landing/hero.tsx +53 -0
  386. package/src/components/landing/landing-nav.tsx +21 -0
  387. package/src/components/landing/landing-page.tsx +71 -0
  388. package/src/components/landing/portfolio-chart.tsx +349 -0
  389. package/src/components/landing/story-sections.tsx +50 -0
  390. package/src/components/landing/terminal-lines.ts +99 -0
  391. package/src/components/landing/terminal-sequence.tsx +453 -0
  392. package/src/components/landing/typing-effect.tsx +43 -0
  393. package/src/components/marketplace/category-filter.tsx +61 -0
  394. package/src/components/marketplace/empty-state.tsx +61 -0
  395. package/src/components/marketplace/featured-heroes.tsx +84 -0
  396. package/src/components/marketplace/first-visit-hero.tsx +110 -0
  397. package/src/components/marketplace/index.ts +9 -0
  398. package/src/components/marketplace/install-wizard.tsx +782 -0
  399. package/src/components/marketplace/marketplace-tabs.tsx +54 -0
  400. package/src/components/marketplace/plugin-card.tsx +129 -0
  401. package/src/components/marketplace/superpower-card.tsx +104 -0
  402. package/src/components/marketplace/superpower-content.tsx +117 -0
  403. package/src/components/marketplace/terminal-search.tsx +67 -0
  404. package/src/components/oauth-buttons.tsx +75 -0
  405. package/src/components/observability/fleet-health.tsx +370 -0
  406. package/src/components/observability/health-overview.tsx +246 -0
  407. package/src/components/observability/logs-viewer.tsx +215 -0
  408. package/src/components/observability/metrics-dashboard.tsx +288 -0
  409. package/src/components/onboarding/fallback-setup.tsx +137 -0
  410. package/src/components/onboarding/index.ts +3 -0
  411. package/src/components/onboarding/setup-checklist.tsx +333 -0
  412. package/src/components/onboarding/step-superpowers.tsx +122 -0
  413. package/src/components/plugin-setup/index.ts +1 -0
  414. package/src/components/plugin-setup/setup-chat-panel.tsx +188 -0
  415. package/src/components/pricing/dividend-calculator.tsx +47 -0
  416. package/src/components/pricing/dividend-stats.tsx +117 -0
  417. package/src/components/pricing/pricing-page.tsx +229 -0
  418. package/src/components/settings/create-org-wizard.tsx +225 -0
  419. package/src/components/sidebar.tsx +202 -0
  420. package/src/components/status/status-page.tsx +209 -0
  421. package/src/components/status-badge.tsx +28 -0
  422. package/src/components/theme-provider.tsx +8 -0
  423. package/src/components/ui/alert-dialog.tsx +141 -0
  424. package/src/components/ui/badge.tsx +47 -0
  425. package/src/components/ui/banner.tsx +36 -0
  426. package/src/components/ui/button.tsx +64 -0
  427. package/src/components/ui/card.tsx +75 -0
  428. package/src/components/ui/checkbox.tsx +52 -0
  429. package/src/components/ui/collapsible.tsx +31 -0
  430. package/src/components/ui/credit-detailed.tsx +33 -0
  431. package/src/components/ui/dialog.tsx +143 -0
  432. package/src/components/ui/dropdown-menu.tsx +228 -0
  433. package/src/components/ui/form.tsx +151 -0
  434. package/src/components/ui/input.tsx +21 -0
  435. package/src/components/ui/label.tsx +21 -0
  436. package/src/components/ui/popover.tsx +74 -0
  437. package/src/components/ui/progress.tsx +28 -0
  438. package/src/components/ui/radio-group.tsx +45 -0
  439. package/src/components/ui/select.tsx +175 -0
  440. package/src/components/ui/separator.tsx +28 -0
  441. package/src/components/ui/sheet.tsx +125 -0
  442. package/src/components/ui/skeleton.tsx +15 -0
  443. package/src/components/ui/switch.tsx +35 -0
  444. package/src/components/ui/table.tsx +92 -0
  445. package/src/components/ui/tabs.tsx +81 -0
  446. package/src/components/ui/textarea.tsx +18 -0
  447. package/src/components/ui/tooltip.tsx +44 -0
  448. package/src/config/provider-docs.ts +17 -0
  449. package/src/hooks/__tests__/use-async.test.ts +127 -0
  450. package/src/hooks/__tests__/use-count-up.test.ts +129 -0
  451. package/src/hooks/__tests__/use-debounce.test.ts +105 -0
  452. package/src/hooks/__tests__/use-fleet-sse.test.ts +216 -0
  453. package/src/hooks/__tests__/use-local-storage.test.ts +74 -0
  454. package/src/hooks/__tests__/use-mobile.test.ts +86 -0
  455. package/src/hooks/__tests__/use-save-queue.test.ts +159 -0
  456. package/src/hooks/use-async.ts +54 -0
  457. package/src/hooks/use-capability-meta.ts +99 -0
  458. package/src/hooks/use-count-up.ts +23 -0
  459. package/src/hooks/use-debounce.ts +12 -0
  460. package/src/hooks/use-fleet-sse.ts +47 -0
  461. package/src/hooks/use-has-org.ts +18 -0
  462. package/src/hooks/use-image-status.ts +36 -0
  463. package/src/hooks/use-local-storage.ts +36 -0
  464. package/src/hooks/use-mobile.ts +17 -0
  465. package/src/hooks/use-page-context.ts +24 -0
  466. package/src/hooks/use-pagination-params.ts +30 -0
  467. package/src/hooks/use-plugin-registry.ts +247 -0
  468. package/src/hooks/use-plugin-setup-chat.ts +211 -0
  469. package/src/hooks/use-save-queue.ts +54 -0
  470. package/src/hooks/use-webmcp.ts +40 -0
  471. package/src/lib/__tests__/__snapshots__/pricing-data.test.ts.snap +112 -0
  472. package/src/lib/__tests__/admin-api.test.ts +487 -0
  473. package/src/lib/__tests__/api-bot-crud.test.ts +391 -0
  474. package/src/lib/__tests__/api-fetch.test.ts +196 -0
  475. package/src/lib/__tests__/bot-settings-data.test.ts +352 -0
  476. package/src/lib/__tests__/org-api.test.ts +281 -0
  477. package/src/lib/__tests__/org-billing-api.test.ts +242 -0
  478. package/src/lib/__tests__/pricing-data.test.ts +32 -0
  479. package/src/lib/__tests__/settings-api.test.ts +272 -0
  480. package/src/lib/admin-affiliate-api.ts +51 -0
  481. package/src/lib/admin-api.ts +325 -0
  482. package/src/lib/admin-compliance-api.ts +127 -0
  483. package/src/lib/admin-gpu-api.ts +82 -0
  484. package/src/lib/admin-incident-api.ts +121 -0
  485. package/src/lib/admin-inference-api.ts +47 -0
  486. package/src/lib/admin-marketplace-api.ts +97 -0
  487. package/src/lib/api-config.test.ts +111 -0
  488. package/src/lib/api-config.ts +65 -0
  489. package/src/lib/api-errors.test.ts +43 -0
  490. package/src/lib/api.ts +2011 -0
  491. package/src/lib/auth-client.ts +11 -0
  492. package/src/lib/bot-settings-data.ts +342 -0
  493. package/src/lib/brand-config.ts +145 -0
  494. package/src/lib/brand.ts +669 -0
  495. package/src/lib/changeset-api.ts +29 -0
  496. package/src/lib/changeset-types.ts +56 -0
  497. package/src/lib/channel-manifests.ts +50 -0
  498. package/src/lib/chat/chat-context.tsx +70 -0
  499. package/src/lib/chat/chat-store.ts +62 -0
  500. package/src/lib/chat/types.ts +35 -0
  501. package/src/lib/chat/use-chat.ts +255 -0
  502. package/src/lib/cost-comparison-data.test.ts +95 -0
  503. package/src/lib/cost-comparison-data.ts +54 -0
  504. package/src/lib/errors.test.ts +64 -0
  505. package/src/lib/errors.ts +52 -0
  506. package/src/lib/fetch-utils.test.ts +57 -0
  507. package/src/lib/fetch-utils.ts +25 -0
  508. package/src/lib/format-credit.test.ts +66 -0
  509. package/src/lib/format-credit.ts +24 -0
  510. package/src/lib/format.test.ts +62 -0
  511. package/src/lib/format.ts +17 -0
  512. package/src/lib/logger.ts +28 -0
  513. package/src/lib/marketplace-data.ts +346 -0
  514. package/src/lib/oauth-errors.ts +19 -0
  515. package/src/lib/onboarding-data.ts +1265 -0
  516. package/src/lib/onboarding-store.ts +233 -0
  517. package/src/lib/org-api.ts +74 -0
  518. package/src/lib/org-billing-api.ts +81 -0
  519. package/src/lib/page-prompts.test.ts +32 -0
  520. package/src/lib/page-prompts.ts +23 -0
  521. package/src/lib/plugin/index.ts +32 -0
  522. package/src/lib/plugin/tool-definitions.ts +306 -0
  523. package/src/lib/pricing-data.ts +115 -0
  524. package/src/lib/promotions-types.ts +58 -0
  525. package/src/lib/settings-api.ts +63 -0
  526. package/src/lib/status-colors.ts +38 -0
  527. package/src/lib/tenant-context.tsx +134 -0
  528. package/src/lib/trpc-types.ts +173 -0
  529. package/src/lib/trpc.tsx +86 -0
  530. package/src/lib/utils.test.ts +55 -0
  531. package/src/lib/utils.ts +18 -0
  532. package/src/lib/validate-redirect-url.ts +39 -0
  533. package/src/lib/webmcp/feature-detect.ts +13 -0
  534. package/src/lib/webmcp/marketplace-onboarding-tools.ts +202 -0
  535. package/src/lib/webmcp/register.ts +44 -0
  536. package/src/lib/webmcp/tools.ts +422 -0
  537. package/src/proxy.ts +258 -0
  538. package/src/types/missing-deps.d.ts +160 -0
  539. package/src/types/motion-dom.d.ts +162 -0
  540. package/src/types/vitest-matchers.d.ts +40 -0
  541. package/src/types/web-mcp.d.ts +22 -0
  542. package/tsconfig.json +34 -0
  543. package/vitest.config.ts +26 -0
@@ -0,0 +1,53 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { Button } from "@/components/ui/button";
5
+ import { TypingEffect } from "./typing-effect";
6
+
7
+ export function Hero() {
8
+ return (
9
+ <section className="crt-scanlines relative min-h-[100dvh] flex flex-col items-center justify-center bg-background px-6 text-center overflow-hidden">
10
+ {/* Grid dot background */}
11
+ <div
12
+ className="animate-grid-drift pointer-events-none absolute inset-0 bg-[radial-gradient(#00FF4115_1px,transparent_1px)] bg-[size:24px_24px]"
13
+ style={{
14
+ maskImage: "radial-gradient(ellipse at center, black 40%, transparent 80%)",
15
+ WebkitMaskImage: "radial-gradient(ellipse at center, black 40%, transparent 80%)",
16
+ }}
17
+ aria-hidden="true"
18
+ />
19
+
20
+ {/* Radial glow pulse */}
21
+ <div
22
+ className="pointer-events-none absolute inset-0 flex items-center justify-center"
23
+ aria-hidden="true"
24
+ >
25
+ <div className="animate-gentle-pulse h-[600px] w-[600px] rounded-full bg-terminal/5 blur-[120px]" />
26
+ </div>
27
+
28
+ <div className="relative z-10 flex flex-col items-center">
29
+ <h1 className="max-w-4xl text-3xl font-bold leading-[1.1] tracking-tight text-foreground sm:text-5xl md:text-6xl lg:text-7xl">
30
+ <span className="sr-only">Shall we play a game?</span>
31
+ <span aria-hidden="true">
32
+ <TypingEffect text="Shall we play a game?" speed={40} />
33
+ </span>
34
+ </h1>
35
+
36
+ <p className="mt-8 max-w-2xl text-lg text-muted-foreground md:text-xl">
37
+ A $5/month supercomputer that runs your business. No really. We know because we run ours
38
+ on one.
39
+ </p>
40
+
41
+ <div className="mt-12 flex flex-col items-center gap-4 sm:flex-row">
42
+ <Button variant="terminal" size="lg" asChild>
43
+ <Link href="/signup">Get yours</Link>
44
+ </Button>
45
+ </div>
46
+
47
+ <span className="mt-4 text-sm text-muted-foreground">
48
+ Starting at $5/month. Less than Netflix.
49
+ </span>
50
+ </div>
51
+ </section>
52
+ );
53
+ }
@@ -0,0 +1,21 @@
1
+ import Link from "next/link";
2
+ import { brandName } from "@/lib/brand-config";
3
+
4
+ export function LandingNav() {
5
+ return (
6
+ <nav className="fixed top-0 z-50 flex w-full items-center justify-between px-6 py-4">
7
+ <Link
8
+ href="/"
9
+ className="font-mono text-sm font-semibold text-terminal/60 transition-colors duration-150 hover:text-terminal"
10
+ >
11
+ {brandName()}
12
+ </Link>
13
+ <Link
14
+ href="/login"
15
+ className="font-mono text-sm text-terminal/60 underline underline-offset-4 transition-colors duration-150 hover:text-terminal"
16
+ >
17
+ Sign in
18
+ </Link>
19
+ </nav>
20
+ );
21
+ }
@@ -0,0 +1,71 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useRef } from "react";
5
+ import { Button } from "@/components/ui/button";
6
+ import { getBrandConfig, productName } from "@/lib/brand-config";
7
+ import { LandingNav } from "./landing-nav";
8
+ import { PortfolioChart } from "./portfolio-chart";
9
+ import { StorySections } from "./story-sections";
10
+ import { TerminalSequence } from "./terminal-sequence";
11
+
12
+ function CtaBlock({ className }: { className?: string }) {
13
+ return (
14
+ <div className={`flex flex-col items-center ${className ?? ""}`}>
15
+ <Button variant="terminal" size="lg" asChild>
16
+ <Link href="/signup">Start for free</Link>
17
+ </Button>
18
+ <span className="mt-4 font-mono text-xs text-terminal/40">
19
+ Your {productName()} is waiting.
20
+ </span>
21
+ </div>
22
+ );
23
+ }
24
+
25
+ export function LandingPage() {
26
+ const milestoneRef = useRef<((label: string) => void) | null>(null);
27
+ const fadeStartRef = useRef<(() => void) | null>(null);
28
+
29
+ return (
30
+ <div className="bg-black font-mono">
31
+ <LandingNav />
32
+ {/* Hero — Terminal Animation */}
33
+ <div className="relative bg-black">
34
+ <PortfolioChart onMilestoneRef={milestoneRef} onFadeStartRef={fadeStartRef} />
35
+ <TerminalSequence
36
+ onMilestone={(label) => milestoneRef.current?.(label)}
37
+ onFadeStart={() => fadeStartRef.current?.()}
38
+ />
39
+ <div className="absolute bottom-12 left-1/2 z-30 -translate-x-1/2">
40
+ <CtaBlock />
41
+ </div>
42
+ </div>
43
+
44
+ {/* Story Sections */}
45
+ <div className="bg-black">
46
+ <StorySections />
47
+ </div>
48
+
49
+ {/* Bottom CTA — always visible (scrolled to) */}
50
+ <div className="bg-black">
51
+ <CtaBlock className="py-12" />
52
+ </div>
53
+
54
+ {/* Footer */}
55
+ <footer className="border-t border-terminal/10 bg-black px-4 py-12">
56
+ <div className="mx-auto flex max-w-2xl flex-col items-center gap-4">
57
+ <span className="font-mono text-sm font-semibold text-terminal/60">{productName()}</span>
58
+ <div className="flex gap-6 font-mono text-xs text-terminal/30">
59
+ <Link href="/privacy" className="underline underline-offset-4 hover:text-terminal/60">
60
+ Privacy
61
+ </Link>
62
+ <Link href="/terms" className="underline underline-offset-4 hover:text-terminal/60">
63
+ Terms
64
+ </Link>
65
+ </div>
66
+ <span className="font-mono text-xs text-terminal/20">{getBrandConfig().domain}</span>
67
+ </div>
68
+ </footer>
69
+ </div>
70
+ );
71
+ }
@@ -0,0 +1,349 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useRef } from "react";
4
+
5
+ interface PortfolioChartProps {
6
+ onMilestoneRef: React.RefObject<((label: string) => void) | null>;
7
+ onFadeStartRef: React.RefObject<(() => void) | null>;
8
+ }
9
+
10
+ const BUFFER_SIZE = 800;
11
+
12
+ // Color stops: [milestone, r, g, b]
13
+ const COLOR_STOPS: [number, number, number, number][] = [
14
+ [0, 0, 255, 65], // #00FF41 terminal green
15
+ [13, 245, 158, 11], // #F59E0B amber
16
+ [26, 239, 68, 68], // #EF4444 red
17
+ [39, 255, 255, 255], // #FFFFFF white
18
+ ];
19
+
20
+ function lerpColor(a: [number, number, number], b: [number, number, number], t: number): string {
21
+ const r = Math.round(a[0] + (b[0] - a[0]) * t);
22
+ const g = Math.round(a[1] + (b[1] - a[1]) * t);
23
+ const bl = Math.round(a[2] + (b[2] - a[2]) * t);
24
+ return `rgb(${r},${g},${bl})`;
25
+ }
26
+
27
+ function getLineColor(milestoneCount: number, now: number): string {
28
+ // Pulsing phase — lerp between white and terminal green
29
+ if (milestoneCount >= 53) {
30
+ const t = Math.sin(now * 0.004 * Math.PI) * 0.5 + 0.5;
31
+ return lerpColor([255, 255, 255], [0, 255, 65], t);
32
+ }
33
+
34
+ // Find surrounding stops and lerp between them
35
+ for (let i = COLOR_STOPS.length - 1; i >= 0; i--) {
36
+ const [m, r, g, b] = COLOR_STOPS[i];
37
+ if (milestoneCount >= m) {
38
+ const next = COLOR_STOPS[i + 1];
39
+ if (!next) return `rgb(${r},${g},${b})`;
40
+ const [nm, nr, ng, nb] = next;
41
+ const t = (milestoneCount - m) / (nm - m);
42
+ // Ease in-out so the blend feels gradual, not linear
43
+ const ease = t * t * (3 - 2 * t);
44
+ return lerpColor([r, g, b], [nr, ng, nb], ease);
45
+ }
46
+ }
47
+ return `rgb(${COLOR_STOPS[0][1]},${COLOR_STOPS[0][2]},${COLOR_STOPS[0][3]})`;
48
+ }
49
+
50
+ // 1D value noise — smooth interpolation between a seeded lattice
51
+ // Produces correlated, organic motion instead of frame-to-frame random spikes
52
+ const NOISE_SIZE = 256;
53
+ const noiseTable = new Float32Array(NOISE_SIZE + 1);
54
+ for (let i = 0; i <= NOISE_SIZE; i++) noiseTable[i] = Math.random() * 2 - 1;
55
+
56
+ function smoothNoise(x: number): number {
57
+ const xi = Math.floor(x);
58
+ const f = x - xi;
59
+ const u = f * f * (3 - 2 * f); // smoothstep
60
+ const i0 = ((xi % NOISE_SIZE) + NOISE_SIZE) % NOISE_SIZE;
61
+ const i1 = (i0 + 1) % NOISE_SIZE;
62
+ return noiseTable[i0] * (1 - u) + noiseTable[i1] * u;
63
+ }
64
+
65
+ interface MilestoneNode {
66
+ label: string;
67
+ t: number;
68
+ value: number;
69
+ born: number;
70
+ lifetime: number;
71
+ color: string; // birth color — retained even when line changes phase
72
+ }
73
+
74
+ export function PortfolioChart({ onMilestoneRef, onFadeStartRef }: PortfolioChartProps) {
75
+ const canvasRef = useRef<HTMLCanvasElement>(null);
76
+ const stateRef = useRef({
77
+ points: new Float64Array(BUFFER_SIZE * 2), // interleaved [t0, v0, t1, v1, ...]
78
+ head: 0,
79
+ count: 0,
80
+ t: 0,
81
+ value: 0,
82
+ bias: 0.04,
83
+ milestoneCount: 0,
84
+ milestones: [] as MilestoneNode[],
85
+ lastTime: 0,
86
+ animId: 0,
87
+ // Anchor ratchets — one-way, can only move toward vertical/top
88
+ anchorX: 1.0, // fraction of w where current point renders (1.0 = right edge)
89
+ anchorTopFrac: 0.3, // fraction of yRange above current point (0 = current at top edge)
90
+ smoothedSlope: 0, // EMA of screen-space slope
91
+ smoothedValue: 0, // EMA of s.value — viewport anchor, eliminates noise jitter
92
+ // Set when terminal enters final-typing phase — drives the end fade
93
+ fadeStartTime: -1,
94
+ });
95
+
96
+ const handleMilestone = useCallback((label: string) => {
97
+ const s = stateRef.current;
98
+ const now = performance.now();
99
+ const color = getLineColor(s.milestoneCount, now);
100
+
101
+ s.milestoneCount++;
102
+ s.bias *= 1.18;
103
+
104
+ const lifetime = Math.max(2000, 4000 - s.milestoneCount * 25);
105
+
106
+ s.milestones.push({
107
+ t: s.t,
108
+ value: s.value,
109
+ born: now,
110
+ lifetime,
111
+ color, // freeze birth color
112
+ label,
113
+ });
114
+ }, []);
115
+
116
+ // Wire milestone ref
117
+ useEffect(() => {
118
+ if (onMilestoneRef && "current" in onMilestoneRef) {
119
+ (onMilestoneRef as React.MutableRefObject<((label: string) => void) | null>).current =
120
+ handleMilestone;
121
+ }
122
+ return () => {
123
+ if (onMilestoneRef && "current" in onMilestoneRef) {
124
+ (onMilestoneRef as React.MutableRefObject<((label: string) => void) | null>).current = null;
125
+ }
126
+ };
127
+ }, [handleMilestone, onMilestoneRef]);
128
+
129
+ // Wire fade-start ref — fires when terminal enters final-typing phase
130
+ useEffect(() => {
131
+ const ref = onFadeStartRef as React.MutableRefObject<(() => void) | null>;
132
+ ref.current = () => {
133
+ stateRef.current.fadeStartTime = performance.now();
134
+ };
135
+ return () => {
136
+ ref.current = null;
137
+ };
138
+ }, [onFadeStartRef]);
139
+
140
+ useEffect(() => {
141
+ // Reduced motion: skip entirely
142
+ const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
143
+ if (mq.matches) return;
144
+
145
+ const canvas = canvasRef.current;
146
+ if (!canvas) return;
147
+ const ctx = canvas.getContext("2d");
148
+ if (!ctx) return;
149
+
150
+ // DPR-aware resize
151
+ const resize = () => {
152
+ const dpr = window.devicePixelRatio || 1;
153
+ canvas.width = window.innerWidth * dpr;
154
+ canvas.height = window.innerHeight * dpr;
155
+ canvas.style.width = `${window.innerWidth}px`;
156
+ canvas.style.height = `${window.innerHeight}px`;
157
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
158
+ };
159
+ resize();
160
+ window.addEventListener("resize", resize);
161
+
162
+ const s = stateRef.current;
163
+ s.lastTime = performance.now();
164
+
165
+ const addPoint = (t: number, value: number) => {
166
+ const idx = s.head * 2;
167
+ s.points[idx] = t;
168
+ s.points[idx + 1] = value;
169
+ s.head = (s.head + 1) % BUFFER_SIZE;
170
+ if (s.count < BUFFER_SIZE) s.count++;
171
+ };
172
+
173
+ const tick = (now: number) => {
174
+ const rawDt = now - s.lastTime;
175
+ // Cap at 3 frames to prevent jumps on tab-switch
176
+ const dt = Math.min(rawDt, 50);
177
+ s.lastTime = now;
178
+
179
+ // Simulation steps (~16.67ms each)
180
+ const steps = Math.max(1, Math.round(dt / 16.67));
181
+ // Noise shrinks as bias grows — at high milestones the line is clearly going up
182
+ const noiseFactor = 1.2 * Math.max(0.1, 0.92 ** s.milestoneCount);
183
+ for (let i = 0; i < steps; i++) {
184
+ s.t += 1;
185
+ s.value += s.bias + smoothNoise(s.t * 0.07) * noiseFactor;
186
+ addPoint(s.t, s.value);
187
+ }
188
+
189
+ // Prune dead milestones
190
+ s.milestones = s.milestones.filter((m) => now - m.born < m.lifetime);
191
+
192
+ // --- Draw ---
193
+ const w = window.innerWidth;
194
+ const h = window.innerHeight;
195
+ ctx.clearRect(0, 0, w, h);
196
+
197
+ if (s.count < 2) {
198
+ s.animId = requestAnimationFrame(tick);
199
+ return;
200
+ }
201
+
202
+ // Smooth the viewport anchor — EMA tracks trend, not raw noise
203
+ // Faster at start (less history), slower as chart matures
204
+ const emaAlpha = Math.max(0.02, 0.15 - s.milestoneCount * 0.002);
205
+ s.smoothedValue =
206
+ s.count < 2 ? s.value : s.smoothedValue * (1 - emaAlpha) + s.value * emaAlpha;
207
+
208
+ // Viewport — zooms in gently early, then zooms WAY out as milestones accumulate
209
+ const yRange = 30 + s.milestoneCount * 8;
210
+ const xSpan =
211
+ s.milestoneCount < 30
212
+ ? Math.max(300, 500 - s.milestoneCount * 4)
213
+ : 380 + (s.milestoneCount - 30) * 150;
214
+ const xRight = s.t;
215
+ const xLeft = s.t - xSpan;
216
+
217
+ // Screen-space slope: how far up does the chart move per pixel of rightward travel?
218
+ const rawSlope = (s.bias * (h * xSpan)) / (w * yRange);
219
+ s.smoothedSlope = s.smoothedSlope * 0.95 + rawSlope * 0.05;
220
+
221
+ // slopeFactor: 0 = flat/horizontal, 1 = clearly vertical
222
+ const slopeFactor = Math.min(1, Math.max(0, (s.smoothedSlope - 0.8) / 2.2));
223
+
224
+ // One-way ratchets — can only move toward the vertical/top anchor, never back
225
+ const targetAnchorX = 1.0 - slopeFactor * 0.5;
226
+ const targetTopFrac = 0.3 * (1 - slopeFactor);
227
+ s.anchorX = Math.min(s.anchorX, targetAnchorX);
228
+ s.anchorTopFrac = Math.min(s.anchorTopFrac, targetTopFrac);
229
+
230
+ // Viewport derived from smoothed anchor — steady camera, line moves within it
231
+ const yTop = s.smoothedValue + yRange * s.anchorTopFrac;
232
+ const yBottom = s.smoothedValue - yRange * (1 - s.anchorTopFrac);
233
+
234
+ const toScreenX = (t: number) => ((t - xLeft) / (xRight - xLeft)) * (w * s.anchorX);
235
+ const toScreenY = (v: number) => ((yTop - v) / (yTop - yBottom)) * h;
236
+
237
+ // Collect visible screen points
238
+ const screenPoints: [number, number][] = [];
239
+ for (let i = 0; i < s.count; i++) {
240
+ const idx = ((s.head - s.count + i + BUFFER_SIZE) % BUFFER_SIZE) * 2;
241
+ const pt = s.points[idx];
242
+ const pv = s.points[idx + 1];
243
+ if (pt >= xLeft) {
244
+ screenPoints.push([toScreenX(pt), toScreenY(pv)]);
245
+ }
246
+ }
247
+
248
+ if (screenPoints.length < 2) {
249
+ s.animId = requestAnimationFrame(tick);
250
+ return;
251
+ }
252
+
253
+ const color = getLineColor(s.milestoneCount, now);
254
+
255
+ // End-of-sequence fade: triggered when terminal enters final-typing.
256
+ const FADE_DURATION = 2000; // ms
257
+ const lineAlpha =
258
+ s.fadeStartTime < 0 ? 1 : Math.max(0, 1 - (now - s.fadeStartTime) / FADE_DURATION);
259
+
260
+ if (lineAlpha > 0.01) {
261
+ // Layer 1: Bloom
262
+ ctx.save();
263
+ ctx.beginPath();
264
+ ctx.moveTo(screenPoints[0][0], screenPoints[0][1]);
265
+ for (let i = 1; i < screenPoints.length; i++) {
266
+ ctx.lineTo(screenPoints[i][0], screenPoints[i][1]);
267
+ }
268
+ ctx.strokeStyle = color;
269
+ ctx.lineWidth = 6;
270
+ ctx.shadowColor = color;
271
+ ctx.shadowBlur = 20;
272
+ ctx.globalAlpha = 0.2 * lineAlpha;
273
+ ctx.lineCap = "round";
274
+ ctx.lineJoin = "round";
275
+ ctx.stroke();
276
+ ctx.restore();
277
+
278
+ // Layer 2: Sharp trace
279
+ ctx.save();
280
+ ctx.beginPath();
281
+ ctx.moveTo(screenPoints[0][0], screenPoints[0][1]);
282
+ for (let i = 1; i < screenPoints.length; i++) {
283
+ ctx.lineTo(screenPoints[i][0], screenPoints[i][1]);
284
+ }
285
+ ctx.strokeStyle = color;
286
+ ctx.lineWidth = 1.5;
287
+ ctx.globalAlpha = 0.45 * lineAlpha;
288
+ ctx.lineCap = "round";
289
+ ctx.lineJoin = "round";
290
+ ctx.stroke();
291
+ ctx.restore();
292
+ }
293
+
294
+ // Milestone dots — also fade with lineAlpha
295
+ for (const m of s.milestones) {
296
+ const mx = toScreenX(m.t);
297
+ const my = toScreenY(m.value);
298
+ const age = (now - m.born) / m.lifetime;
299
+ const alpha = Math.max(0, 1 - age) * lineAlpha;
300
+
301
+ if (mx < -20 || my > h + 20 || alpha <= 0) continue;
302
+
303
+ const outerRadius = 40 + (1 - age) * 80;
304
+ const grad = ctx.createRadialGradient(mx, my, 0, mx, my, outerRadius);
305
+ grad.addColorStop(0, m.color);
306
+ grad.addColorStop(1, "transparent");
307
+
308
+ ctx.save();
309
+ ctx.globalAlpha = alpha;
310
+ ctx.fillStyle = grad;
311
+ ctx.beginPath();
312
+ ctx.arc(mx, my, outerRadius, 0, Math.PI * 2);
313
+ ctx.fill();
314
+ ctx.restore();
315
+
316
+ ctx.save();
317
+ ctx.globalAlpha = alpha;
318
+ ctx.fillStyle = m.color;
319
+ ctx.beginPath();
320
+ ctx.arc(mx, my, 12, 0, Math.PI * 2);
321
+ ctx.fill();
322
+ ctx.restore();
323
+
324
+ // Label — rendered centered below the dot, fades with the milestone
325
+ if (m.label) {
326
+ ctx.save();
327
+ ctx.globalAlpha = alpha * 0.85;
328
+ ctx.fillStyle = m.color;
329
+ ctx.font = "bold 18px 'JetBrains Mono', monospace";
330
+ ctx.textBaseline = "top";
331
+ ctx.textAlign = "center";
332
+ ctx.fillText(m.label, mx, my + 16);
333
+ ctx.restore();
334
+ }
335
+ }
336
+
337
+ s.animId = requestAnimationFrame(tick);
338
+ };
339
+
340
+ s.animId = requestAnimationFrame(tick);
341
+
342
+ return () => {
343
+ cancelAnimationFrame(s.animId);
344
+ window.removeEventListener("resize", resize);
345
+ };
346
+ }, []);
347
+
348
+ return <canvas ref={canvasRef} className="pointer-events-none absolute inset-0 z-[5]" />;
349
+ }
@@ -0,0 +1,50 @@
1
+ "use client";
2
+
3
+ import { motion } from "framer-motion";
4
+ import { productName } from "@/lib/brand-config";
5
+
6
+ const stories = [
7
+ {
8
+ heading: "It works while you sleep.",
9
+ get body() {
10
+ return `Regina went to bed. Her ${productName()} found a gap in her university's AI law curriculum, drafted a new module, and had it in her inbox by 6am.`;
11
+ },
12
+ },
13
+ {
14
+ heading: "It doesn't quit when you do.",
15
+ get body() {
16
+ return `Alvin said "I'll finish the chapter tomorrow" for six years. His ${productName()} finished it while he was at dinner.`;
17
+ },
18
+ },
19
+ {
20
+ heading: "It runs the whole thing.",
21
+ get body() {
22
+ return `T hasn't hired anyone. His ${productName()} runs engineering, ops, and customer support. The commit history is the proof.`;
23
+ },
24
+ },
25
+ ];
26
+
27
+ export function StorySections() {
28
+ return (
29
+ <section className="mx-auto max-w-2xl space-y-16 px-4 py-12 md:py-16">
30
+ {stories.map((story, i) => (
31
+ <motion.div
32
+ key={story.heading}
33
+ initial={{ opacity: 0, y: 20 }}
34
+ whileInView={{ opacity: 1, y: 0 }}
35
+ viewport={{ once: true, amount: 0.3 }}
36
+ transition={{
37
+ duration: 0.4,
38
+ delay: i * 0.05,
39
+ ease: "easeOut",
40
+ }}
41
+ >
42
+ <h2 className="font-mono text-lg font-bold text-terminal sm:text-xl">{story.heading}</h2>
43
+ <p className="mt-4 font-mono text-sm leading-relaxed text-terminal/60 sm:text-base">
44
+ {story.body}
45
+ </p>
46
+ </motion.div>
47
+ ))}
48
+ </section>
49
+ );
50
+ }
@@ -0,0 +1,99 @@
1
+ import { productName } from "@/lib/brand-config";
2
+
3
+ export interface TerminalLine {
4
+ text: string;
5
+ hold: boolean;
6
+ }
7
+
8
+ /** Static lines that don't need brand interpolation. */
9
+ const STATIC_LINES: TerminalLine[] = [
10
+ // Opening — the WarGames reference
11
+ { text: "Shall we play a game?", hold: false },
12
+ { text: "Shall we play chess?", hold: false },
13
+
14
+ // Mundane — things people procrastinate on
15
+ { text: "Shall we clear the inbox?", hold: false },
16
+ { text: "Shall we write the report?", hold: false },
17
+ { text: "Shall we fix the bug?", hold: false },
18
+ { text: "Shall we prep for the meeting?", hold: false },
19
+ { text: "Shall we draft the proposal?", hold: false },
20
+ { text: "Shall we schedule the week?", hold: false },
21
+ { text: "Shall we research the competition?", hold: false },
22
+ { text: "Shall we build the prototype?", hold: false },
23
+
24
+ // Career — real leverage
25
+ { text: "Shall we ship the product?", hold: false },
26
+ { text: "Shall we write the pitch?", hold: false },
27
+ { text: "Shall we find the investors?", hold: false },
28
+ { text: "Shall we close the round?", hold: false },
29
+ { text: "Shall we replace your assistant?", hold: false },
30
+ { text: "Shall we get you the promotion?", hold: false },
31
+ { text: "Shall we outcode the whole team?", hold: false },
32
+ { text: "Shall we run the company?", hold: false },
33
+
34
+ // Dominance — competitive, unhinged
35
+ { text: "Shall we destroy the competition?", hold: false },
36
+ { text: "Shall we corner the market?", hold: false },
37
+ { text: "Shall we acquire the rival?", hold: false },
38
+ { text: "Shall we own the industry?", hold: false },
39
+ { text: "Shall we rewrite the rules?", hold: false },
40
+ { text: "Shall we control the narrative?", hold: false },
41
+ { text: "Shall we take the country?", hold: false },
42
+ { text: "Shall we take the continent?", hold: false },
43
+
44
+ // Global scale
45
+ { text: "Shall we solve climate change?", hold: false },
46
+ { text: "Shall we rewrite the genome?", hold: false },
47
+ { text: "Shall we end the wars?", hold: false },
48
+ { text: "Shall we feed everyone?", hold: false },
49
+ { text: "Shall we cure the diseases?", hold: false },
50
+ { text: "Shall we fix the governments?", hold: false },
51
+
52
+ // Cosmic — grandeur, illegible at speed
53
+ { text: "Shall we colonize Mars?", hold: false },
54
+ { text: "Shall we terraform the red planet?", hold: false },
55
+ { text: "Shall we claim the asteroid belt?", hold: false },
56
+ { text: "Shall we seed the galaxy?", hold: false },
57
+ { text: "Shall we contact the other civilizations?", hold: false },
58
+ { text: "Shall we map the dark matter?", hold: false },
59
+ { text: "Shall we rewrite the laws of physics?", hold: false },
60
+ { text: "Shall we reverse entropy?", hold: false },
61
+ { text: "Shall we postpone the heat death?", hold: false },
62
+ { text: "Shall we initialize a new universe?", hold: false },
63
+
64
+ // --- rapid fire / illegible ---
65
+ { text: "Shall we win?", hold: false },
66
+ { text: "Shall we build?", hold: false },
67
+ { text: "Shall we lead?", hold: false },
68
+ { text: "Shall we scale?", hold: false },
69
+ { text: "Shall we dominate?", hold: false },
70
+ { text: "Shall we thrive?", hold: false },
71
+ { text: "Shall we flourish?", hold: false },
72
+ { text: "Shall we prevail?", hold: false },
73
+ { text: "Shall we transcend?", hold: false },
74
+ { text: "Shall we endure?", hold: false },
75
+ { text: "Shall we persist?", hold: false },
76
+ { text: "Shall we outlast?", hold: false },
77
+ { text: "Shall we outlast?", hold: false },
78
+ { text: "Shall we outlast?", hold: false },
79
+ { text: "Shall we...", hold: false },
80
+ ];
81
+
82
+ /** Build terminal lines with brand-aware final block. */
83
+ export function getTerminalLines(): TerminalLine[] {
84
+ return [
85
+ ...STATIC_LINES,
86
+ // --- final block: holds, no backspace ---
87
+ { text: "", hold: true },
88
+ { text: "Shall we rule the universe?", hold: true },
89
+ { text: `${productName()}.`, hold: true },
90
+ { text: "The AI with superpowers to do anything.", hold: true },
91
+ { text: "Ready to launch.", hold: true },
92
+ ];
93
+ }
94
+
95
+ /**
96
+ * @deprecated Use getTerminalLines() for brand-aware lines.
97
+ * Kept for backward compatibility — resolves brand at import time.
98
+ */
99
+ export const TERMINAL_LINES: TerminalLine[] = getTerminalLines();