@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.
- package/.env.paperclip +18 -0
- package/.env.wopr +18 -0
- package/README.md +36 -0
- package/biome.json +52 -0
- package/next.config.ts +45 -0
- package/package.json +84 -0
- package/postcss.config.mjs +7 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/window.svg +1 -0
- package/src/__tests__/__snapshots__/layout-snapshots.test.tsx.snap +741 -0
- package/src/__tests__/account-page-redirect.test.tsx +73 -0
- package/src/__tests__/account-switcher.test.tsx +85 -0
- package/src/__tests__/activity-page.test.tsx +176 -0
- package/src/__tests__/add-payment-method-dialog.test.tsx +160 -0
- package/src/__tests__/admin-api.test.ts +244 -0
- package/src/__tests__/admin-gpu-api.test.ts +188 -0
- package/src/__tests__/admin-guard.test.tsx +79 -0
- package/src/__tests__/admin-marketplace-api.test.ts +179 -0
- package/src/__tests__/admin-middleware.test.ts +157 -0
- package/src/__tests__/admin-tenant-table.test.tsx +95 -0
- package/src/__tests__/affiliate-dashboard.test.tsx +178 -0
- package/src/__tests__/api-401-redirect.test.ts +78 -0
- package/src/__tests__/api-client.test.ts +316 -0
- package/src/__tests__/api-config.test.ts +89 -0
- package/src/__tests__/api-control-instance.test.ts +69 -0
- package/src/__tests__/api-fleet-resources.test.ts +52 -0
- package/src/__tests__/api-fleet-trpc.test.ts +252 -0
- package/src/__tests__/api-get-instance-config.test.ts +41 -0
- package/src/__tests__/api-null-guards.test.ts +244 -0
- package/src/__tests__/api-rename-instance.test.ts +60 -0
- package/src/__tests__/api-update-instance-config.test.ts +60 -0
- package/src/__tests__/audit-log-table-pagination.test.tsx +136 -0
- package/src/__tests__/auth-client.test.ts +87 -0
- package/src/__tests__/auth-password-reset.test.tsx +435 -0
- package/src/__tests__/auth-redirect.test.tsx +60 -0
- package/src/__tests__/auth.test.tsx +269 -0
- package/src/__tests__/auto-topup-card.test.tsx +257 -0
- package/src/__tests__/backups-tab.test.tsx +221 -0
- package/src/__tests__/billing-byok-callout.test.tsx +76 -0
- package/src/__tests__/billing-layout-nav-hidden.test.tsx +47 -0
- package/src/__tests__/billing-payment-org-invoices.test.tsx +123 -0
- package/src/__tests__/billing.test.tsx +509 -0
- package/src/__tests__/bot-settings/resources-tab.test.tsx +119 -0
- package/src/__tests__/bot-settings/storage-tab.test.tsx +80 -0
- package/src/__tests__/bot-settings/vps-info-panel.test.tsx +108 -0
- package/src/__tests__/bot-settings/vps-upgrade-card.test.tsx +52 -0
- package/src/__tests__/bot-settings-data-control.test.ts +49 -0
- package/src/__tests__/bot-settings-restart.test.tsx +149 -0
- package/src/__tests__/bot-settings.test.tsx +678 -0
- package/src/__tests__/brand.test.ts +335 -0
- package/src/__tests__/buy-credits-panel.test.tsx +249 -0
- package/src/__tests__/buy-crypto-credits-panel.test.tsx +178 -0
- package/src/__tests__/capability-conflicts.test.ts +88 -0
- package/src/__tests__/capability-resolver.test.tsx +173 -0
- package/src/__tests__/changeset-detail.test.tsx +156 -0
- package/src/__tests__/channel-setup-logger.test.ts +22 -0
- package/src/__tests__/channel-setup-toast.test.tsx +60 -0
- package/src/__tests__/channel-wizard.test.tsx +505 -0
- package/src/__tests__/chat/ambient-dot.test.tsx +35 -0
- package/src/__tests__/chat/chat-input.test.tsx +78 -0
- package/src/__tests__/chat/chat-message.test.tsx +45 -0
- package/src/__tests__/chat/chat-panel.test.tsx +111 -0
- package/src/__tests__/chat/chat-widget.test.tsx +82 -0
- package/src/__tests__/chat-store.test.ts +87 -0
- package/src/__tests__/command-center.test.tsx +246 -0
- package/src/__tests__/compliance-retention-edit.test.tsx +134 -0
- package/src/__tests__/coupon-input.test.tsx +119 -0
- package/src/__tests__/create-instance.test.tsx +96 -0
- package/src/__tests__/create-org-wizard.test.tsx +200 -0
- package/src/__tests__/credit-balance.test.tsx +103 -0
- package/src/__tests__/credits.test.tsx +376 -0
- package/src/__tests__/csrf-middleware.test.ts +198 -0
- package/src/__tests__/degraded-state-banner.test.tsx +130 -0
- package/src/__tests__/dividend-calculator.test.tsx +20 -0
- package/src/__tests__/dividend-stats.test.tsx +64 -0
- package/src/__tests__/dividend.test.tsx +169 -0
- package/src/__tests__/dockerfile.test.ts +110 -0
- package/src/__tests__/email-verification-banner.test.tsx +64 -0
- package/src/__tests__/env-example.test.ts +25 -0
- package/src/__tests__/error-boundaries.test.tsx +64 -0
- package/src/__tests__/fetch-pricing.test.ts +121 -0
- package/src/__tests__/field-oauth.test.tsx +302 -0
- package/src/__tests__/fixtures/mock-manifests-data.js +372 -0
- package/src/__tests__/fixtures/mock-manifests.ts +24 -0
- package/src/__tests__/fleet-health-timestamp.test.tsx +101 -0
- package/src/__tests__/fleet-health-update.test.tsx +83 -0
- package/src/__tests__/format-credit.test.ts +58 -0
- package/src/__tests__/gpu-dashboard.test.tsx +236 -0
- package/src/__tests__/hosted-usage-date-range.test.tsx +54 -0
- package/src/__tests__/instance-detail.test.tsx +571 -0
- package/src/__tests__/instance-list.test.tsx +230 -0
- package/src/__tests__/landing-hero.test.tsx +27 -0
- package/src/__tests__/landing-nav.test.tsx +24 -0
- package/src/__tests__/layout-snapshots.test.tsx +167 -0
- package/src/__tests__/logger.test.ts +54 -0
- package/src/__tests__/login-page-redirect.test.tsx +142 -0
- package/src/__tests__/manifest-validation.test.ts +126 -0
- package/src/__tests__/marketplace-admin.test.tsx +151 -0
- package/src/__tests__/marketplace.test.tsx +609 -0
- package/src/__tests__/merge-api-rates.test.ts +70 -0
- package/src/__tests__/middleware.test.ts +690 -0
- package/src/__tests__/network-page.test.tsx +100 -0
- package/src/__tests__/next-config-headers.test.ts +28 -0
- package/src/__tests__/not-found.test.tsx +26 -0
- package/src/__tests__/notifications.test.tsx +128 -0
- package/src/__tests__/oauth-buttons.test.tsx +101 -0
- package/src/__tests__/oauth-error-mapping.test.tsx +97 -0
- package/src/__tests__/observability.test.tsx +541 -0
- package/src/__tests__/onboarding-data.test.ts +363 -0
- package/src/__tests__/onboarding-page.test.tsx +113 -0
- package/src/__tests__/onboarding-store.test.ts +121 -0
- package/src/__tests__/org-billing-api.test.tsx +70 -0
- package/src/__tests__/org-billing-null-guards.test.ts +64 -0
- package/src/__tests__/org-billing-page.test.tsx +124 -0
- package/src/__tests__/plugin-definition.test.ts +43 -0
- package/src/__tests__/plugin-install-flow.test.tsx +535 -0
- package/src/__tests__/plugin-registry.test.tsx +475 -0
- package/src/__tests__/plugin-setup/setup-chat-panel.test.ts +142 -0
- package/src/__tests__/plugin-setup/use-plugin-setup-chat.test.ts +49 -0
- package/src/__tests__/plugin-tool-definitions.test.ts +51 -0
- package/src/__tests__/plugin-tool-sync.test.ts +59 -0
- package/src/__tests__/portfolio-chart.test.tsx +24 -0
- package/src/__tests__/pricing.test.tsx +107 -0
- package/src/__tests__/promotion-form.test.tsx +180 -0
- package/src/__tests__/promotions-list.test.tsx +194 -0
- package/src/__tests__/provider-key-api.test.ts +134 -0
- package/src/__tests__/resend-verification-button.test.tsx +104 -0
- package/src/__tests__/sanitize-redirect-url.test.ts +47 -0
- package/src/__tests__/secrets-audit-pagination.test.tsx +139 -0
- package/src/__tests__/settings.test.tsx +937 -0
- package/src/__tests__/setup-checklist.test.tsx +274 -0
- package/src/__tests__/setup.ts +82 -0
- package/src/__tests__/smoke.test.tsx +10 -0
- package/src/__tests__/snapshot-api.test.ts +104 -0
- package/src/__tests__/status-api.test.ts +46 -0
- package/src/__tests__/status-badge.test.tsx +33 -0
- package/src/__tests__/status-colors.test.ts +83 -0
- package/src/__tests__/status-page.test.tsx +86 -0
- package/src/__tests__/step-superpowers.test.tsx +218 -0
- package/src/__tests__/story-sections.test.tsx +24 -0
- package/src/__tests__/superpower-content-sanitize.test.tsx +87 -0
- package/src/__tests__/superpower-content.test.tsx +44 -0
- package/src/__tests__/suspension-banner.test.tsx +140 -0
- package/src/__tests__/tenant-context.test.tsx +146 -0
- package/src/__tests__/tenant-keys-api.test.ts +114 -0
- package/src/__tests__/tenant-table-pagination.test.tsx +124 -0
- package/src/__tests__/terminal-log-cleanup.test.tsx +51 -0
- package/src/__tests__/terminal-sequence.test.tsx +28 -0
- package/src/__tests__/transaction-history.test.tsx +325 -0
- package/src/__tests__/trpc-types.test.ts +102 -0
- package/src/__tests__/use-capability-meta.test.ts +161 -0
- package/src/__tests__/use-chat.test.ts +616 -0
- package/src/__tests__/use-has-org.test.ts +44 -0
- package/src/__tests__/use-image-status.test.ts +77 -0
- package/src/__tests__/use-pagination-params.test.ts +88 -0
- package/src/__tests__/use-plugin-setup-chat-stale-closure.test.ts +53 -0
- package/src/__tests__/use-webmcp.test.ts +119 -0
- package/src/__tests__/validate-elevenlabs-key.test.ts +95 -0
- package/src/__tests__/validate-redirect-url.test.ts +61 -0
- package/src/__tests__/verify-page.test.tsx +140 -0
- package/src/__tests__/verify-redirect.test.tsx +41 -0
- package/src/__tests__/verify-result-banner.test.tsx +66 -0
- package/src/__tests__/webmcp-feature-detect.test.ts +54 -0
- package/src/__tests__/webmcp-hook.test.tsx +72 -0
- package/src/__tests__/webmcp-marketplace-onboarding-tools.test.ts +185 -0
- package/src/__tests__/webmcp-register.test.ts +103 -0
- package/src/__tests__/webmcp-set-provider.test.ts +47 -0
- package/src/__tests__/webmcp-tools.test.ts +348 -0
- package/src/app/(auth)/error.tsx +72 -0
- package/src/app/(auth)/forgot-password/page.tsx +137 -0
- package/src/app/(auth)/layout.tsx +14 -0
- package/src/app/(auth)/loading.tsx +26 -0
- package/src/app/(auth)/login/page.tsx +188 -0
- package/src/app/(auth)/reset-password/page.tsx +169 -0
- package/src/app/(auth)/signup/page.tsx +309 -0
- package/src/app/(dashboard)/billing/credits/page.tsx +209 -0
- package/src/app/(dashboard)/billing/error.tsx +72 -0
- package/src/app/(dashboard)/billing/layout.tsx +73 -0
- package/src/app/(dashboard)/billing/loading.tsx +41 -0
- package/src/app/(dashboard)/billing/payment/page.tsx +639 -0
- package/src/app/(dashboard)/billing/plans/page.tsx +58 -0
- package/src/app/(dashboard)/billing/referrals/page.tsx +7 -0
- package/src/app/(dashboard)/billing/usage/hosted/page.tsx +348 -0
- package/src/app/(dashboard)/billing/usage/page.tsx +663 -0
- package/src/app/(dashboard)/changesets/[id]/changeset-detail-client.tsx +400 -0
- package/src/app/(dashboard)/changesets/[id]/error.tsx +57 -0
- package/src/app/(dashboard)/changesets/[id]/loading.tsx +23 -0
- package/src/app/(dashboard)/changesets/[id]/page.tsx +10 -0
- package/src/app/(dashboard)/changesets/error.tsx +72 -0
- package/src/app/(dashboard)/changesets/page.tsx +10 -0
- package/src/app/(dashboard)/chat/page.tsx +74 -0
- package/src/app/(dashboard)/dashboard/bots/[id]/settings/page.tsx +10 -0
- package/src/app/(dashboard)/dashboard/network/page.tsx +97 -0
- package/src/app/(dashboard)/dashboard/page.tsx +13 -0
- package/src/app/(dashboard)/error.tsx +72 -0
- package/src/app/(dashboard)/layout.tsx +113 -0
- package/src/app/(dashboard)/loading.tsx +27 -0
- package/src/app/(dashboard)/marketplace/[plugin]/page.tsx +548 -0
- package/src/app/(dashboard)/marketplace/error.tsx +72 -0
- package/src/app/(dashboard)/marketplace/loading.tsx +27 -0
- package/src/app/(dashboard)/marketplace/page.tsx +268 -0
- package/src/app/(dashboard)/not-found.tsx +46 -0
- package/src/app/(dashboard)/onboarding/page.tsx +267 -0
- package/src/app/(dashboard)/settings/account/page.tsx +132 -0
- package/src/app/(dashboard)/settings/activity/page.tsx +280 -0
- package/src/app/(dashboard)/settings/api-keys/page.tsx +530 -0
- package/src/app/(dashboard)/settings/brain/page.tsx +412 -0
- package/src/app/(dashboard)/settings/error.tsx +72 -0
- package/src/app/(dashboard)/settings/layout.tsx +114 -0
- package/src/app/(dashboard)/settings/loading.tsx +31 -0
- package/src/app/(dashboard)/settings/notifications/page.tsx +216 -0
- package/src/app/(dashboard)/settings/org/page.tsx +617 -0
- package/src/app/(dashboard)/settings/profile/page.tsx +510 -0
- package/src/app/(dashboard)/settings/providers/page.tsx +842 -0
- package/src/app/(dashboard)/settings/secrets/page.tsx +658 -0
- package/src/app/(dashboard)/settings/security/page.tsx +1133 -0
- package/src/app/admin/accounting/loading.tsx +32 -0
- package/src/app/admin/accounting/page.tsx +5 -0
- package/src/app/admin/affiliates/loading.tsx +32 -0
- package/src/app/admin/affiliates/page.tsx +5 -0
- package/src/app/admin/audit/loading.tsx +32 -0
- package/src/app/admin/audit/page.tsx +5 -0
- package/src/app/admin/billing-health/loading.tsx +17 -0
- package/src/app/admin/billing-health/page.tsx +10 -0
- package/src/app/admin/compliance/page.tsx +5 -0
- package/src/app/admin/error.tsx +72 -0
- package/src/app/admin/gpu/loading.tsx +38 -0
- package/src/app/admin/gpu/page.tsx +5 -0
- package/src/app/admin/incidents/page.tsx +10 -0
- package/src/app/admin/inference/loading.tsx +32 -0
- package/src/app/admin/inference/page.tsx +5 -0
- package/src/app/admin/layout.tsx +44 -0
- package/src/app/admin/loading.tsx +32 -0
- package/src/app/admin/marketplace/loading.tsx +32 -0
- package/src/app/admin/marketplace/page.tsx +5 -0
- package/src/app/admin/migrations/loading.tsx +22 -0
- package/src/app/admin/migrations/page.tsx +5 -0
- package/src/app/admin/onboarding/loading.tsx +18 -0
- package/src/app/admin/onboarding/page.tsx +5 -0
- package/src/app/admin/promotions/[id]/edit/loading.tsx +16 -0
- package/src/app/admin/promotions/[id]/edit/page.tsx +56 -0
- package/src/app/admin/promotions/[id]/loading.tsx +15 -0
- package/src/app/admin/promotions/[id]/page.tsx +311 -0
- package/src/app/admin/promotions/loading.tsx +21 -0
- package/src/app/admin/promotions/new/loading.tsx +16 -0
- package/src/app/admin/promotions/new/page.tsx +12 -0
- package/src/app/admin/promotions/page.tsx +266 -0
- package/src/app/admin/rate-overrides/loading.tsx +17 -0
- package/src/app/admin/rate-overrides/page.tsx +290 -0
- package/src/app/admin/roles/loading.tsx +27 -0
- package/src/app/admin/roles/page.tsx +5 -0
- package/src/app/admin/tenants/loading.tsx +32 -0
- package/src/app/admin/tenants/page.tsx +5 -0
- package/src/app/apple-icon.tsx +32 -0
- package/src/app/auth/callback/[provider]/page.tsx +104 -0
- package/src/app/auth/verify/page.tsx +224 -0
- package/src/app/channels/error.tsx +72 -0
- package/src/app/channels/loading.tsx +29 -0
- package/src/app/channels/page.tsx +262 -0
- package/src/app/channels/setup/[plugin]/page.tsx +136 -0
- package/src/app/error.tsx +72 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/fleet/error.tsx +72 -0
- package/src/app/fleet/health/page.tsx +9 -0
- package/src/app/fleet/layout.tsx +14 -0
- package/src/app/fleet/loading.tsx +33 -0
- package/src/app/fleet/page.tsx +5 -0
- package/src/app/global-error.tsx +96 -0
- package/src/app/globals.css +251 -0
- package/src/app/icon.svg +4 -0
- package/src/app/instances/[id]/instance-detail-client.tsx +1298 -0
- package/src/app/instances/[id]/page.tsx +10 -0
- package/src/app/instances/error.tsx +72 -0
- package/src/app/instances/instance-list-client.tsx +540 -0
- package/src/app/instances/loading.tsx +33 -0
- package/src/app/instances/new/create-instance-client.tsx +377 -0
- package/src/app/instances/new/page.tsx +9 -0
- package/src/app/instances/page.tsx +9 -0
- package/src/app/layout.tsx +83 -0
- package/src/app/not-found.tsx +38 -0
- package/src/app/og/route.tsx +50 -0
- package/src/app/page.tsx +39 -0
- package/src/app/plugins/error.tsx +72 -0
- package/src/app/plugins/layout.tsx +14 -0
- package/src/app/plugins/loading.tsx +30 -0
- package/src/app/plugins/page.tsx +555 -0
- package/src/app/pricing/error.tsx +72 -0
- package/src/app/pricing/loading.tsx +25 -0
- package/src/app/pricing/page.tsx +20 -0
- package/src/app/privacy/page.tsx +406 -0
- package/src/app/robots.ts +9 -0
- package/src/app/sitemap.ts +11 -0
- package/src/app/status/error.tsx +72 -0
- package/src/app/status/loading.tsx +21 -0
- package/src/app/status/page.tsx +20 -0
- package/src/app/terms/page.tsx +414 -0
- package/src/components/account-switcher.tsx +82 -0
- package/src/components/admin/accounting-dashboard.tsx +190 -0
- package/src/components/admin/admin-guard.tsx +36 -0
- package/src/components/admin/admin-nav.tsx +71 -0
- package/src/components/admin/affiliate-dashboard.tsx +564 -0
- package/src/components/admin/audit-log-table.tsx +336 -0
- package/src/components/admin/billing-health-dashboard.test.tsx +40 -0
- package/src/components/admin/billing-health-dashboard.tsx +416 -0
- package/src/components/admin/bulk-actions-bar.test.tsx +92 -0
- package/src/components/admin/bulk-actions-bar.tsx +80 -0
- package/src/components/admin/bulk-export-dialog.test.tsx +75 -0
- package/src/components/admin/bulk-export-dialog.tsx +189 -0
- package/src/components/admin/bulk-grant-dialog.test.tsx +81 -0
- package/src/components/admin/bulk-grant-dialog.tsx +147 -0
- package/src/components/admin/bulk-preview-dialog.test.tsx +72 -0
- package/src/components/admin/bulk-preview-dialog.tsx +106 -0
- package/src/components/admin/bulk-reactivate-dialog.test.tsx +51 -0
- package/src/components/admin/bulk-reactivate-dialog.tsx +55 -0
- package/src/components/admin/bulk-select-all-banner.test.tsx +36 -0
- package/src/components/admin/bulk-select-all-banner.tsx +44 -0
- package/src/components/admin/bulk-suspend-dialog.test.tsx +77 -0
- package/src/components/admin/bulk-suspend-dialog.tsx +129 -0
- package/src/components/admin/bulk-undo-toast.test.tsx +66 -0
- package/src/components/admin/bulk-undo-toast.tsx +121 -0
- package/src/components/admin/compliance-dashboard.tsx +1341 -0
- package/src/components/admin/gpu-dashboard.tsx +552 -0
- package/src/components/admin/grant-credits-dialog.tsx +121 -0
- package/src/components/admin/incident-dashboard.test.tsx +44 -0
- package/src/components/admin/incident-dashboard.tsx +717 -0
- package/src/components/admin/inference-dashboard.tsx +415 -0
- package/src/components/admin/marketplace-admin.tsx +765 -0
- package/src/components/admin/migrations-dashboard.tsx +404 -0
- package/src/components/admin/onboarding-dashboard.tsx +422 -0
- package/src/components/admin/promotions/promotion-form.tsx +440 -0
- package/src/components/admin/roles-dashboard.tsx +278 -0
- package/src/components/admin/suspend-dialog.tsx +98 -0
- package/src/components/admin/tenant-notes-panel.tsx +134 -0
- package/src/components/admin/tenant-row-actions.tsx +78 -0
- package/src/components/admin/tenant-table.tsx +339 -0
- package/src/components/auth/auth-error.tsx +22 -0
- package/src/components/auth/auth-redirect.tsx +18 -0
- package/src/components/auth/auth-shell.tsx +25 -0
- package/src/components/auth/email-verification-banner.tsx +25 -0
- package/src/components/auth/email-verification-result-banner.tsx +70 -0
- package/src/components/auth/resend-verification-button.tsx +94 -0
- package/src/components/auth/wopr-wordmark.tsx +19 -0
- package/src/components/billing/add-payment-method-dialog.tsx +267 -0
- package/src/components/billing/affiliate-dashboard.tsx +300 -0
- package/src/components/billing/auto-topup-card.tsx +432 -0
- package/src/components/billing/buy-credits-panel.tsx +180 -0
- package/src/components/billing/buy-crypto-credits-panel.tsx +96 -0
- package/src/components/billing/byok-callout.tsx +87 -0
- package/src/components/billing/coupon-input.tsx +86 -0
- package/src/components/billing/credit-balance.tsx +79 -0
- package/src/components/billing/degraded-state-banner.tsx +95 -0
- package/src/components/billing/dividend-banner.tsx +97 -0
- package/src/components/billing/dividend-eligibility.tsx +86 -0
- package/src/components/billing/dividend-pool-stats.tsx +86 -0
- package/src/components/billing/first-dividend-dialog.tsx +109 -0
- package/src/components/billing/low-balance-banner.tsx +50 -0
- package/src/components/billing/org-billing-page.tsx +360 -0
- package/src/components/billing/suspension-banner.tsx +53 -0
- package/src/components/billing/transaction-history.tsx +239 -0
- package/src/components/bot-settings/__tests__/bot-settings-client.test.tsx +205 -0
- package/src/components/bot-settings/backups-tab.tsx +377 -0
- package/src/components/bot-settings/bot-settings-client.tsx +1712 -0
- package/src/components/bot-settings/resources-tab.tsx +203 -0
- package/src/components/bot-settings/storage-tab.tsx +248 -0
- package/src/components/bot-settings/vps-info-panel.tsx +132 -0
- package/src/components/bot-settings/vps-upgrade-card.tsx +110 -0
- package/src/components/capability/CapabilityResolver.tsx +113 -0
- package/src/components/channel-wizard/field-interactive.tsx +48 -0
- package/src/components/channel-wizard/field-oauth.tsx +181 -0
- package/src/components/channel-wizard/field-paste.tsx +47 -0
- package/src/components/channel-wizard/field-qr.tsx +302 -0
- package/src/components/channel-wizard/index.ts +6 -0
- package/src/components/channel-wizard/step-renderer.tsx +103 -0
- package/src/components/channel-wizard/wizard.tsx +200 -0
- package/src/components/chat/ambient-dot.tsx +32 -0
- package/src/components/chat/chat-input.tsx +56 -0
- package/src/components/chat/chat-message.tsx +36 -0
- package/src/components/chat/chat-panel.tsx +138 -0
- package/src/components/chat/chat-widget.tsx +41 -0
- package/src/components/chat/index.ts +5 -0
- package/src/components/dashboard/command-center.tsx +614 -0
- package/src/components/instances/friends-tab.test.tsx +265 -0
- package/src/components/instances/friends-tab.tsx +721 -0
- package/src/components/landing/hero.tsx +53 -0
- package/src/components/landing/landing-nav.tsx +21 -0
- package/src/components/landing/landing-page.tsx +71 -0
- package/src/components/landing/portfolio-chart.tsx +349 -0
- package/src/components/landing/story-sections.tsx +50 -0
- package/src/components/landing/terminal-lines.ts +99 -0
- package/src/components/landing/terminal-sequence.tsx +453 -0
- package/src/components/landing/typing-effect.tsx +43 -0
- package/src/components/marketplace/category-filter.tsx +61 -0
- package/src/components/marketplace/empty-state.tsx +61 -0
- package/src/components/marketplace/featured-heroes.tsx +84 -0
- package/src/components/marketplace/first-visit-hero.tsx +110 -0
- package/src/components/marketplace/index.ts +9 -0
- package/src/components/marketplace/install-wizard.tsx +782 -0
- package/src/components/marketplace/marketplace-tabs.tsx +54 -0
- package/src/components/marketplace/plugin-card.tsx +129 -0
- package/src/components/marketplace/superpower-card.tsx +104 -0
- package/src/components/marketplace/superpower-content.tsx +117 -0
- package/src/components/marketplace/terminal-search.tsx +67 -0
- package/src/components/oauth-buttons.tsx +75 -0
- package/src/components/observability/fleet-health.tsx +370 -0
- package/src/components/observability/health-overview.tsx +246 -0
- package/src/components/observability/logs-viewer.tsx +215 -0
- package/src/components/observability/metrics-dashboard.tsx +288 -0
- package/src/components/onboarding/fallback-setup.tsx +137 -0
- package/src/components/onboarding/index.ts +3 -0
- package/src/components/onboarding/setup-checklist.tsx +333 -0
- package/src/components/onboarding/step-superpowers.tsx +122 -0
- package/src/components/plugin-setup/index.ts +1 -0
- package/src/components/plugin-setup/setup-chat-panel.tsx +188 -0
- package/src/components/pricing/dividend-calculator.tsx +47 -0
- package/src/components/pricing/dividend-stats.tsx +117 -0
- package/src/components/pricing/pricing-page.tsx +229 -0
- package/src/components/settings/create-org-wizard.tsx +225 -0
- package/src/components/sidebar.tsx +202 -0
- package/src/components/status/status-page.tsx +209 -0
- package/src/components/status-badge.tsx +28 -0
- package/src/components/theme-provider.tsx +8 -0
- package/src/components/ui/alert-dialog.tsx +141 -0
- package/src/components/ui/badge.tsx +47 -0
- package/src/components/ui/banner.tsx +36 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +75 -0
- package/src/components/ui/checkbox.tsx +52 -0
- package/src/components/ui/collapsible.tsx +31 -0
- package/src/components/ui/credit-detailed.tsx +33 -0
- package/src/components/ui/dialog.tsx +143 -0
- package/src/components/ui/dropdown-menu.tsx +228 -0
- package/src/components/ui/form.tsx +151 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +21 -0
- package/src/components/ui/popover.tsx +74 -0
- package/src/components/ui/progress.tsx +28 -0
- package/src/components/ui/radio-group.tsx +45 -0
- package/src/components/ui/select.tsx +175 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +125 -0
- package/src/components/ui/skeleton.tsx +15 -0
- package/src/components/ui/switch.tsx +35 -0
- package/src/components/ui/table.tsx +92 -0
- package/src/components/ui/tabs.tsx +81 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +44 -0
- package/src/config/provider-docs.ts +17 -0
- package/src/hooks/__tests__/use-async.test.ts +127 -0
- package/src/hooks/__tests__/use-count-up.test.ts +129 -0
- package/src/hooks/__tests__/use-debounce.test.ts +105 -0
- package/src/hooks/__tests__/use-fleet-sse.test.ts +216 -0
- package/src/hooks/__tests__/use-local-storage.test.ts +74 -0
- package/src/hooks/__tests__/use-mobile.test.ts +86 -0
- package/src/hooks/__tests__/use-save-queue.test.ts +159 -0
- package/src/hooks/use-async.ts +54 -0
- package/src/hooks/use-capability-meta.ts +99 -0
- package/src/hooks/use-count-up.ts +23 -0
- package/src/hooks/use-debounce.ts +12 -0
- package/src/hooks/use-fleet-sse.ts +47 -0
- package/src/hooks/use-has-org.ts +18 -0
- package/src/hooks/use-image-status.ts +36 -0
- package/src/hooks/use-local-storage.ts +36 -0
- package/src/hooks/use-mobile.ts +17 -0
- package/src/hooks/use-page-context.ts +24 -0
- package/src/hooks/use-pagination-params.ts +30 -0
- package/src/hooks/use-plugin-registry.ts +247 -0
- package/src/hooks/use-plugin-setup-chat.ts +211 -0
- package/src/hooks/use-save-queue.ts +54 -0
- package/src/hooks/use-webmcp.ts +40 -0
- package/src/lib/__tests__/__snapshots__/pricing-data.test.ts.snap +112 -0
- package/src/lib/__tests__/admin-api.test.ts +487 -0
- package/src/lib/__tests__/api-bot-crud.test.ts +391 -0
- package/src/lib/__tests__/api-fetch.test.ts +196 -0
- package/src/lib/__tests__/bot-settings-data.test.ts +352 -0
- package/src/lib/__tests__/org-api.test.ts +281 -0
- package/src/lib/__tests__/org-billing-api.test.ts +242 -0
- package/src/lib/__tests__/pricing-data.test.ts +32 -0
- package/src/lib/__tests__/settings-api.test.ts +272 -0
- package/src/lib/admin-affiliate-api.ts +51 -0
- package/src/lib/admin-api.ts +325 -0
- package/src/lib/admin-compliance-api.ts +127 -0
- package/src/lib/admin-gpu-api.ts +82 -0
- package/src/lib/admin-incident-api.ts +121 -0
- package/src/lib/admin-inference-api.ts +47 -0
- package/src/lib/admin-marketplace-api.ts +97 -0
- package/src/lib/api-config.test.ts +111 -0
- package/src/lib/api-config.ts +65 -0
- package/src/lib/api-errors.test.ts +43 -0
- package/src/lib/api.ts +2011 -0
- package/src/lib/auth-client.ts +11 -0
- package/src/lib/bot-settings-data.ts +342 -0
- package/src/lib/brand-config.ts +145 -0
- package/src/lib/brand.ts +669 -0
- package/src/lib/changeset-api.ts +29 -0
- package/src/lib/changeset-types.ts +56 -0
- package/src/lib/channel-manifests.ts +50 -0
- package/src/lib/chat/chat-context.tsx +70 -0
- package/src/lib/chat/chat-store.ts +62 -0
- package/src/lib/chat/types.ts +35 -0
- package/src/lib/chat/use-chat.ts +255 -0
- package/src/lib/cost-comparison-data.test.ts +95 -0
- package/src/lib/cost-comparison-data.ts +54 -0
- package/src/lib/errors.test.ts +64 -0
- package/src/lib/errors.ts +52 -0
- package/src/lib/fetch-utils.test.ts +57 -0
- package/src/lib/fetch-utils.ts +25 -0
- package/src/lib/format-credit.test.ts +66 -0
- package/src/lib/format-credit.ts +24 -0
- package/src/lib/format.test.ts +62 -0
- package/src/lib/format.ts +17 -0
- package/src/lib/logger.ts +28 -0
- package/src/lib/marketplace-data.ts +346 -0
- package/src/lib/oauth-errors.ts +19 -0
- package/src/lib/onboarding-data.ts +1265 -0
- package/src/lib/onboarding-store.ts +233 -0
- package/src/lib/org-api.ts +74 -0
- package/src/lib/org-billing-api.ts +81 -0
- package/src/lib/page-prompts.test.ts +32 -0
- package/src/lib/page-prompts.ts +23 -0
- package/src/lib/plugin/index.ts +32 -0
- package/src/lib/plugin/tool-definitions.ts +306 -0
- package/src/lib/pricing-data.ts +115 -0
- package/src/lib/promotions-types.ts +58 -0
- package/src/lib/settings-api.ts +63 -0
- package/src/lib/status-colors.ts +38 -0
- package/src/lib/tenant-context.tsx +134 -0
- package/src/lib/trpc-types.ts +173 -0
- package/src/lib/trpc.tsx +86 -0
- package/src/lib/utils.test.ts +55 -0
- package/src/lib/utils.ts +18 -0
- package/src/lib/validate-redirect-url.ts +39 -0
- package/src/lib/webmcp/feature-detect.ts +13 -0
- package/src/lib/webmcp/marketplace-onboarding-tools.ts +202 -0
- package/src/lib/webmcp/register.ts +44 -0
- package/src/lib/webmcp/tools.ts +422 -0
- package/src/proxy.ts +258 -0
- package/src/types/missing-deps.d.ts +160 -0
- package/src/types/motion-dom.d.ts +162 -0
- package/src/types/vitest-matchers.d.ts +40 -0
- package/src/types/web-mcp.d.ts +22 -0
- package/tsconfig.json +34 -0
- 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();
|