vibecarbon 0.1.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/LICENSE +663 -0
- package/README.md +188 -0
- package/carbon/.claude/hooks/task-completed-gate.sh +19 -0
- package/carbon/.claude/hooks/teammate-idle-gate.sh +29 -0
- package/carbon/.claude/settings.json +29 -0
- package/carbon/.cursor/rules/vibecarbon.mdc +6 -0
- package/carbon/.dockerignore +57 -0
- package/carbon/.env.example +219 -0
- package/carbon/.github/copilot-instructions.md +3 -0
- package/carbon/.github/workflows/deploy.yml +346 -0
- package/carbon/.github/workflows/vibecarbon-build.yml +81 -0
- package/carbon/.windsurfrules +74 -0
- package/carbon/AGENTS.md +422 -0
- package/carbon/CLAUDE.md +3 -0
- package/carbon/DEVELOPMENT.md +187 -0
- package/carbon/Dockerfile +98 -0
- package/carbon/LICENSE +21 -0
- package/carbon/PRODUCTION.md +364 -0
- package/carbon/README.md +193 -0
- package/carbon/TESTING.md +138 -0
- package/carbon/backup/Dockerfile +75 -0
- package/carbon/backup/backup.sh +95 -0
- package/carbon/backup/compose-backup.sh +140 -0
- package/carbon/biome.json +83 -0
- package/carbon/cloud-init/docker-ce-setup.yaml +55 -0
- package/carbon/cloud-init/k3s/master-init.sh +215 -0
- package/carbon/cloud-init/k3s/supabase-init.sh +167 -0
- package/carbon/cloud-init/k3s/worker-init.sh +147 -0
- package/carbon/components.json +24 -0
- package/carbon/content/blog/authentication-guide.mdx +34 -0
- package/carbon/content/blog/getting-started.mdx +41 -0
- package/carbon/content/changelog/v0-1-0.mdx +40 -0
- package/carbon/content/docs/analytics.mdx +90 -0
- package/carbon/content/docs/authentication.mdx +231 -0
- package/carbon/content/docs/background-jobs.mdx +116 -0
- package/carbon/content/docs/cli.mdx +630 -0
- package/carbon/content/docs/database.mdx +236 -0
- package/carbon/content/docs/deployment.mdx +227 -0
- package/carbon/content/docs/development.mdx +238 -0
- package/carbon/content/docs/environments.mdx +84 -0
- package/carbon/content/docs/getting-started.mdx +112 -0
- package/carbon/content/docs/legal/privacy-policy.mdx +89 -0
- package/carbon/content/docs/legal/terms-of-service.mdx +99 -0
- package/carbon/content/docs/optional-services.mdx +160 -0
- package/carbon/db/Dockerfile +23 -0
- package/carbon/docker-compose.dev-init.yml +5 -0
- package/carbon/docker-compose.metabase.override.yml +14 -0
- package/carbon/docker-compose.metabase.prod.yml +28 -0
- package/carbon/docker-compose.metabase.yml +84 -0
- package/carbon/docker-compose.n8n.override.yml +14 -0
- package/carbon/docker-compose.n8n.prod.yml +31 -0
- package/carbon/docker-compose.n8n.yml +97 -0
- package/carbon/docker-compose.observability.override.yml +18 -0
- package/carbon/docker-compose.observability.prod.yml +28 -0
- package/carbon/docker-compose.observability.yml +125 -0
- package/carbon/docker-compose.override.yml +28 -0
- package/carbon/docker-compose.prod.yml +294 -0
- package/carbon/docker-compose.yml +508 -0
- package/carbon/docker-entrypoint.sh +12 -0
- package/carbon/ha/activate-standby.sh +52 -0
- package/carbon/ha/primary-init.sql +36 -0
- package/carbon/ha/standby-init.sh +74 -0
- package/carbon/k8s/LICENSE +99 -0
- package/carbon/k8s/README.md +272 -0
- package/carbon/k8s/base/app/deployment.yaml +130 -0
- package/carbon/k8s/base/app/hpa.yaml +44 -0
- package/carbon/k8s/base/app/kustomization.yaml +10 -0
- package/carbon/k8s/base/app/network-policy.yaml +124 -0
- package/carbon/k8s/base/app/pdb.yaml +14 -0
- package/carbon/k8s/base/app/rbac.yaml +36 -0
- package/carbon/k8s/base/app/service.yaml +16 -0
- package/carbon/k8s/base/backup/cronjob.yaml +120 -0
- package/carbon/k8s/base/backup/kustomization.yaml +7 -0
- package/carbon/k8s/base/backup/network-policy.yaml +31 -0
- package/carbon/k8s/base/backup/rbac.yaml +7 -0
- package/carbon/k8s/base/cluster-autoscaler/deployment.yaml +103 -0
- package/carbon/k8s/base/cluster-autoscaler/kustomization.yaml +17 -0
- package/carbon/k8s/base/cluster-autoscaler/rbac.yaml +109 -0
- package/carbon/k8s/base/config/configmap.yaml +48 -0
- package/carbon/k8s/base/config/kustomization.yaml +8 -0
- package/carbon/k8s/base/hetzner-ccm/kustomization.yaml +9 -0
- package/carbon/k8s/base/hetzner-csi/kustomization.yaml +9 -0
- package/carbon/k8s/base/kustomization.yaml +43 -0
- package/carbon/k8s/base/namespace.yaml +7 -0
- package/carbon/k8s/base/network-policies.yaml +95 -0
- package/carbon/k8s/base/registry/kustomization.yaml +5 -0
- package/carbon/k8s/base/registry/local-registry.yaml +193 -0
- package/carbon/k8s/base/traefik/certificate.yaml +21 -0
- package/carbon/k8s/base/traefik/configmap.yaml +13 -0
- package/carbon/k8s/base/traefik/deployment.yaml +165 -0
- package/carbon/k8s/base/traefik/ingressroute.yaml +141 -0
- package/carbon/k8s/base/traefik/kustomization.yaml +11 -0
- package/carbon/k8s/base/traefik/middleware.yaml +109 -0
- package/carbon/k8s/base/traefik/network-policy.yaml +92 -0
- package/carbon/k8s/base/traefik/service.yaml +38 -0
- package/carbon/k8s/flux/README.md +49 -0
- package/carbon/k8s/flux/clusters/primary/vibecarbon.yaml +128 -0
- package/carbon/k8s/flux/clusters/standby/vibecarbon.yaml +83 -0
- package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-release.yaml +38 -0
- package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-repository.yaml +16 -0
- package/carbon/k8s/gitops/cert-manager-webhook-hetzner/kustomization.yaml +10 -0
- package/carbon/k8s/gitops/supabase/helm-release.yaml +66 -0
- package/carbon/k8s/gitops/supabase/helm-repository.yaml +18 -0
- package/carbon/k8s/gitops/supabase/kustomization.yaml +33 -0
- package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-cloudflare.yaml +51 -0
- package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-hetzner.yaml +59 -0
- package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-manual.yaml +43 -0
- package/carbon/k8s/infra/cert-manager-resources/kustomization.yaml +15 -0
- package/carbon/k8s/infra/kustomization.yaml +21 -0
- package/carbon/k8s/infra/traefik-crds/kustomization.yaml +10 -0
- package/carbon/k8s/overlays/local/configmap.yaml +23 -0
- package/carbon/k8s/overlays/local/ingressroute-studio.yaml +104 -0
- package/carbon/k8s/overlays/local/kustomization.yaml +215 -0
- package/carbon/k8s/overlays/local/namespace.yaml +8 -0
- package/carbon/k8s/overlays/local/secrets.yaml +32 -0
- package/carbon/k8s/overlays/production/kustomization.yaml +34 -0
- package/carbon/k8s/test-local.sh +318 -0
- package/carbon/k8s/values/supabase.values.yaml +133 -0
- package/carbon/package.json +154 -0
- package/carbon/runtime.Dockerfile +17 -0
- package/carbon/scripts/_dev-jwt.mjs +45 -0
- package/carbon/scripts/check-rls.ts +53 -0
- package/carbon/scripts/dev-init.js +191 -0
- package/carbon/scripts/dev.js +191 -0
- package/carbon/scripts/docker-down.js +63 -0
- package/carbon/scripts/docker-logs.js +59 -0
- package/carbon/scripts/docker-up.js +222 -0
- package/carbon/scripts/generate-dev-configs.sh +131 -0
- package/carbon/scripts/generate-rss.ts +116 -0
- package/carbon/scripts/generate-sitemap.ts +102 -0
- package/carbon/scripts/k8s-apply.js +71 -0
- package/carbon/scripts/k8s-delete.js +75 -0
- package/carbon/scripts/lib/manifest.js +176 -0
- package/carbon/scripts/secret-scan.mjs +278 -0
- package/carbon/scripts/validate-dev-configs.sh +101 -0
- package/carbon/src/client/App.tsx +202 -0
- package/carbon/src/client/assets/hyperformant-dark.svg +29 -0
- package/carbon/src/client/assets/hyperformant-light.svg +29 -0
- package/carbon/src/client/assets/logos/aider.svg +1 -0
- package/carbon/src/client/assets/logos/antigravity.svg +1 -0
- package/carbon/src/client/assets/logos/bolt.svg +1 -0
- package/carbon/src/client/assets/logos/claude-code.svg +1 -0
- package/carbon/src/client/assets/logos/copilot.svg +1 -0
- package/carbon/src/client/assets/logos/cursor.svg +1 -0
- package/carbon/src/client/assets/logos/gemini-cli.svg +1 -0
- package/carbon/src/client/assets/logos/lovable.svg +1 -0
- package/carbon/src/client/assets/logos/openai-codex.svg +1 -0
- package/carbon/src/client/assets/logos/v0.svg +1 -0
- package/carbon/src/client/assets/logos/windsurf.svg +1 -0
- package/carbon/src/client/assets/vibecarbon-icon.svg +19 -0
- package/carbon/src/client/assets/vibecarbon-logo-dark.svg +29 -0
- package/carbon/src/client/assets/vibecarbon-logo-light.svg +29 -0
- package/carbon/src/client/components/AIIntegrationSection.tsx +120 -0
- package/carbon/src/client/components/AppSidebar.tsx +760 -0
- package/carbon/src/client/components/ArchitectureSection.tsx +46 -0
- package/carbon/src/client/components/CTAFooter.tsx +59 -0
- package/carbon/src/client/components/ComparisonSection.tsx +132 -0
- package/carbon/src/client/components/ContentPanel.tsx +66 -0
- package/carbon/src/client/components/ContentSkeleton.tsx +21 -0
- package/carbon/src/client/components/ErrorBoundary.tsx +46 -0
- package/carbon/src/client/components/FAQSection.tsx +76 -0
- package/carbon/src/client/components/FileUpload.tsx +210 -0
- package/carbon/src/client/components/HeaderActions.tsx +17 -0
- package/carbon/src/client/components/Hero.tsx +608 -0
- package/carbon/src/client/components/ImpersonationBanner.tsx +31 -0
- package/carbon/src/client/components/LanguageSwitcher.tsx +67 -0
- package/carbon/src/client/components/Logo.tsx +87 -0
- package/carbon/src/client/components/LogoStrip.tsx +76 -0
- package/carbon/src/client/components/MetricsStrip.tsx +48 -0
- package/carbon/src/client/components/Nav.tsx +195 -0
- package/carbon/src/client/components/NewsletterSignup.tsx +147 -0
- package/carbon/src/client/components/NotificationDrawer.tsx +171 -0
- package/carbon/src/client/components/PageHeader.tsx +24 -0
- package/carbon/src/client/components/PlanGate.tsx +36 -0
- package/carbon/src/client/components/PricingSection.tsx +371 -0
- package/carbon/src/client/components/SEO.tsx +34 -0
- package/carbon/src/client/components/SmoothScroll.tsx +45 -0
- package/carbon/src/client/components/TechStackSection.tsx +165 -0
- package/carbon/src/client/components/WorkflowSection.tsx +604 -0
- package/carbon/src/client/components/admin/DockerLogs.tsx +276 -0
- package/carbon/src/client/components/admin/PerformanceMetrics.tsx +230 -0
- package/carbon/src/client/components/admin/ServiceCard.tsx +45 -0
- package/carbon/src/client/components/admin/ServicesStatus.tsx +296 -0
- package/carbon/src/client/components/architecture/ArchitectureDiagram.tsx +413 -0
- package/carbon/src/client/components/auth/AuthProvider.tsx +466 -0
- package/carbon/src/client/components/effects/FilmGrainOverlay.tsx +35 -0
- package/carbon/src/client/components/effects/FresnelEdge.tsx +49 -0
- package/carbon/src/client/components/effects/GlowTracker.tsx +46 -0
- package/carbon/src/client/components/effects/SparkBurst.tsx +145 -0
- package/carbon/src/client/components/effects/VGlowEffect.tsx +83 -0
- package/carbon/src/client/components/effects/index.ts +2 -0
- package/carbon/src/client/components/layouts/SidebarLayout.tsx +20 -0
- package/carbon/src/client/components/scroll/ScrollSection.tsx +76 -0
- package/carbon/src/client/components/scroll/ScrollytellingProvider.tsx +81 -0
- package/carbon/src/client/components/scroll/index.ts +2 -0
- package/carbon/src/client/components/ui/accordion.tsx +71 -0
- package/carbon/src/client/components/ui/alert-dialog.tsx +162 -0
- package/carbon/src/client/components/ui/alert.tsx +73 -0
- package/carbon/src/client/components/ui/aspect-ratio.tsx +22 -0
- package/carbon/src/client/components/ui/avatar.tsx +91 -0
- package/carbon/src/client/components/ui/badge.tsx +50 -0
- package/carbon/src/client/components/ui/breadcrumb.tsx +100 -0
- package/carbon/src/client/components/ui/button-group.tsx +78 -0
- package/carbon/src/client/components/ui/button.tsx +120 -0
- package/carbon/src/client/components/ui/calendar.tsx +182 -0
- package/carbon/src/client/components/ui/card.tsx +85 -0
- package/carbon/src/client/components/ui/carousel.tsx +227 -0
- package/carbon/src/client/components/ui/chart.tsx +357 -0
- package/carbon/src/client/components/ui/checkbox.tsx +27 -0
- package/carbon/src/client/components/ui/collapsible.tsx +15 -0
- package/carbon/src/client/components/ui/command.tsx +178 -0
- package/carbon/src/client/components/ui/context-menu.tsx +233 -0
- package/carbon/src/client/components/ui/dialog.tsx +132 -0
- package/carbon/src/client/components/ui/drawer.tsx +118 -0
- package/carbon/src/client/components/ui/dropdown-menu.tsx +242 -0
- package/carbon/src/client/components/ui/empty.tsx +94 -0
- package/carbon/src/client/components/ui/field.tsx +226 -0
- package/carbon/src/client/components/ui/hover-card.tsx +44 -0
- package/carbon/src/client/components/ui/input-group.tsx +146 -0
- package/carbon/src/client/components/ui/input-otp.tsx +83 -0
- package/carbon/src/client/components/ui/input.tsx +20 -0
- package/carbon/src/client/components/ui/item.tsx +188 -0
- package/carbon/src/client/components/ui/kbd.tsx +26 -0
- package/carbon/src/client/components/ui/label.tsx +21 -0
- package/carbon/src/client/components/ui/menubar.tsx +250 -0
- package/carbon/src/client/components/ui/navigation-menu.tsx +155 -0
- package/carbon/src/client/components/ui/pagination.tsx +102 -0
- package/carbon/src/client/components/ui/popover.tsx +75 -0
- package/carbon/src/client/components/ui/progress.tsx +66 -0
- package/carbon/src/client/components/ui/radio-group.tsx +36 -0
- package/carbon/src/client/components/ui/resizable.tsx +46 -0
- package/carbon/src/client/components/ui/scroll-area.tsx +48 -0
- package/carbon/src/client/components/ui/select.tsx +186 -0
- package/carbon/src/client/components/ui/separator.tsx +21 -0
- package/carbon/src/client/components/ui/sheet.tsx +124 -0
- package/carbon/src/client/components/ui/sidebar.tsx +702 -0
- package/carbon/src/client/components/ui/skeleton.tsx +13 -0
- package/carbon/src/client/components/ui/slider.tsx +57 -0
- package/carbon/src/client/components/ui/sonner.tsx +45 -0
- package/carbon/src/client/components/ui/spinner.tsx +15 -0
- package/carbon/src/client/components/ui/switch.tsx +30 -0
- package/carbon/src/client/components/ui/table.tsx +89 -0
- package/carbon/src/client/components/ui/tabs.tsx +73 -0
- package/carbon/src/client/components/ui/textarea.tsx +18 -0
- package/carbon/src/client/components/ui/toggle-group.tsx +87 -0
- package/carbon/src/client/components/ui/toggle.tsx +44 -0
- package/carbon/src/client/components/ui/tooltip.tsx +56 -0
- package/carbon/src/client/hooks/api/index.ts +14 -0
- package/carbon/src/client/hooks/api/useAuthSettings.ts +80 -0
- package/carbon/src/client/hooks/api/useSubscription.ts +87 -0
- package/carbon/src/client/hooks/use-mobile.ts +21 -0
- package/carbon/src/client/hooks/useMousePosition.ts +91 -0
- package/carbon/src/client/hooks/useNotifications.tsx +124 -0
- package/carbon/src/client/hooks/useOrganizationMembers.ts +127 -0
- package/carbon/src/client/hooks/useOrganizations.tsx +230 -0
- package/carbon/src/client/hooks/usePackageManager.ts +69 -0
- package/carbon/src/client/hooks/useReducedMotion.ts +20 -0
- package/carbon/src/client/hooks/useRunningServices.ts +114 -0
- package/carbon/src/client/hooks/useScrollProgress.ts +56 -0
- package/carbon/src/client/index.css +467 -0
- package/carbon/src/client/index.html +56 -0
- package/carbon/src/client/lib/admin-services.ts +151 -0
- package/carbon/src/client/lib/api.ts +32 -0
- package/carbon/src/client/lib/blog.ts +35 -0
- package/carbon/src/client/lib/changelog.ts +33 -0
- package/carbon/src/client/lib/docs-search.ts +101 -0
- package/carbon/src/client/lib/docs.ts +37 -0
- package/carbon/src/client/lib/i18n.ts +32 -0
- package/carbon/src/client/lib/supabase.ts +72 -0
- package/carbon/src/client/lib/tailwind-colors.ts +357 -0
- package/carbon/src/client/lib/theme.ts +117 -0
- package/carbon/src/client/lib/utils.ts +22 -0
- package/carbon/src/client/locales/de.json +529 -0
- package/carbon/src/client/locales/en.json +461 -0
- package/carbon/src/client/locales/es.json +529 -0
- package/carbon/src/client/locales/fr.json +529 -0
- package/carbon/src/client/locales/pt.json +529 -0
- package/carbon/src/client/main.tsx +56 -0
- package/carbon/src/client/mdx.d.ts +13 -0
- package/carbon/src/client/pages/ApiDocs.tsx +76 -0
- package/carbon/src/client/pages/AuthCallback.tsx +34 -0
- package/carbon/src/client/pages/Blog.tsx +167 -0
- package/carbon/src/client/pages/Changelog.tsx +171 -0
- package/carbon/src/client/pages/Charts.tsx +388 -0
- package/carbon/src/client/pages/Checkout.tsx +227 -0
- package/carbon/src/client/pages/Contact.tsx +174 -0
- package/carbon/src/client/pages/Dashboard.tsx +368 -0
- package/carbon/src/client/pages/Docs.tsx +372 -0
- package/carbon/src/client/pages/ForgotPassword.tsx +111 -0
- package/carbon/src/client/pages/Home.tsx +187 -0
- package/carbon/src/client/pages/Legal.tsx +100 -0
- package/carbon/src/client/pages/Login.tsx +408 -0
- package/carbon/src/client/pages/MFAVerify.tsx +156 -0
- package/carbon/src/client/pages/NotFound.tsx +21 -0
- package/carbon/src/client/pages/Onboarding.tsx +246 -0
- package/carbon/src/client/pages/ResetPassword.tsx +200 -0
- package/carbon/src/client/pages/UIComponents.tsx +390 -0
- package/carbon/src/client/pages/admin/ContactSubmissions.tsx +220 -0
- package/carbon/src/client/pages/admin/Dashboard.tsx +24 -0
- package/carbon/src/client/pages/admin/Infrastructure.tsx +257 -0
- package/carbon/src/client/pages/admin/Jobs.tsx +225 -0
- package/carbon/src/client/pages/admin/Logs.tsx +18 -0
- package/carbon/src/client/pages/admin/Newsletter.tsx +300 -0
- package/carbon/src/client/pages/admin/Notifications.tsx +603 -0
- package/carbon/src/client/pages/admin/Organizations.tsx +306 -0
- package/carbon/src/client/pages/admin/Settings.tsx +314 -0
- package/carbon/src/client/pages/admin/Theme.tsx +465 -0
- package/carbon/src/client/pages/admin/Users.tsx +365 -0
- package/carbon/src/client/pages/api-docs.css +156 -0
- package/carbon/src/client/pages/organizations/Details.tsx +200 -0
- package/carbon/src/client/pages/organizations/Members.tsx +402 -0
- package/carbon/src/client/pages/settings/Billing.tsx +473 -0
- package/carbon/src/client/pages/settings/Profile.tsx +160 -0
- package/carbon/src/client/pages/settings/Security.tsx +341 -0
- package/carbon/src/client/public/favicon.svg +19 -0
- package/carbon/src/client/public/robots.txt +8 -0
- package/carbon/src/server/billing/index.ts +104 -0
- package/carbon/src/server/billing/provider.ts +81 -0
- package/carbon/src/server/billing/providers/paddle.ts +314 -0
- package/carbon/src/server/billing/providers/polar.ts +325 -0
- package/carbon/src/server/billing/providers/stripe.ts +233 -0
- package/carbon/src/server/emails/templates.ts +116 -0
- package/carbon/src/server/index.ts +554 -0
- package/carbon/src/server/lib/auth.ts +6 -0
- package/carbon/src/server/lib/email.ts +64 -0
- package/carbon/src/server/lib/env.ts +112 -0
- package/carbon/src/server/lib/errors.ts +21 -0
- package/carbon/src/server/lib/logger.ts +17 -0
- package/carbon/src/server/lib/rate-limiter.ts +288 -0
- package/carbon/src/server/lib/request.ts +34 -0
- package/carbon/src/server/lib/stripe.ts +42 -0
- package/carbon/src/server/lib/supabase.ts +65 -0
- package/carbon/src/server/middleware/requirePlan.ts +80 -0
- package/carbon/src/server/routes/_internal/services-status.ts +958 -0
- package/carbon/src/server/routes/_internal/verify-role.ts +185 -0
- package/carbon/src/server/routes/health.ts +48 -0
- package/carbon/src/server/routes/v1/admin/contact.ts +128 -0
- package/carbon/src/server/routes/v1/admin/jobs.ts +171 -0
- package/carbon/src/server/routes/v1/admin/newsletter.ts +237 -0
- package/carbon/src/server/routes/v1/auth.ts +390 -0
- package/carbon/src/server/routes/v1/billing.ts +718 -0
- package/carbon/src/server/routes/v1/contact.ts +93 -0
- package/carbon/src/server/routes/v1/index.ts +1333 -0
- package/carbon/src/server/routes/v1/newsletter.ts +181 -0
- package/carbon/src/server/routes/v1/performance.ts +157 -0
- package/carbon/src/server/routes/v1/stats.ts +170 -0
- package/carbon/src/server/routes/v1/theme.ts +106 -0
- package/carbon/src/server/routes/webhooks/billing.ts +376 -0
- package/carbon/src/server/routes/webhooks/stripe.ts +276 -0
- package/carbon/src/server/types.ts +11 -0
- package/carbon/src/shared/pricing.ts +155 -0
- package/carbon/src/shared/types.ts +338 -0
- package/carbon/supabase/migrations/00001_init.sql +717 -0
- package/carbon/supabase/migrations/00002_theme_settings.sql +13 -0
- package/carbon/supabase/migrations/00003_pg_cron.sql +121 -0
- package/carbon/supabase/migrations/00004_contact_newsletter.sql +81 -0
- package/carbon/supabase/migrations/00005_localization_languages.sql +22 -0
- package/carbon/supabase/seed.sql +16 -0
- package/carbon/tests/_helpers/app.ts +45 -0
- package/carbon/tests/_helpers/env.ts +37 -0
- package/carbon/tests/_helpers/factories.ts +69 -0
- package/carbon/tests/_helpers/jwt.ts +23 -0
- package/carbon/tests/_helpers/setup-integration.ts +20 -0
- package/carbon/tests/_helpers/setup-rtl.ts +12 -0
- package/carbon/tests/component/ErrorBoundary.test.tsx +53 -0
- package/carbon/tests/component/use-auth-settings.test.tsx +119 -0
- package/carbon/tests/integration/server/routes/contact.test.ts +162 -0
- package/carbon/tests/integration/server/routes/health.test.ts +61 -0
- package/carbon/tests/structural/i18n-parity.test.ts +42 -0
- package/carbon/tests/unit/client/utils.test.ts +49 -0
- package/carbon/tests/unit/shared/pricing.test.ts +93 -0
- package/carbon/tsconfig.json +27 -0
- package/carbon/tsconfig.server.json +27 -0
- package/carbon/tsconfig.test.json +9 -0
- package/carbon/vite.config.ts +110 -0
- package/carbon/vitest.config.ts +74 -0
- package/carbon/volumes/db/jwt.sql +57 -0
- package/carbon/volumes/db/metabase-init.sh +29 -0
- package/carbon/volumes/db/n8n-init.sh +25 -0
- package/carbon/volumes/db/realtime.sql +33 -0
- package/carbon/volumes/db/roles.sql +93 -0
- package/carbon/volumes/db/set-passwords.sh +12 -0
- package/carbon/volumes/db/super-admin.dev.sql +113 -0
- package/carbon/volumes/db/super-admin.generated.sql +113 -0
- package/carbon/volumes/db/super-admin.sql +114 -0
- package/carbon/volumes/grafana/dashboards/logs.json +179 -0
- package/carbon/volumes/grafana/dashboards/overview.json +523 -0
- package/carbon/volumes/grafana/dashboards/postgresql.json +337 -0
- package/carbon/volumes/grafana/dashboards.dev/logs.json +179 -0
- package/carbon/volumes/grafana/dashboards.dev/overview.json +156 -0
- package/carbon/volumes/grafana/dashboards.dev/postgresql.json +337 -0
- package/carbon/volumes/grafana/provisioning/dashboards/dashboards.dev.yml +16 -0
- package/carbon/volumes/grafana/provisioning/dashboards/dashboards.yml +16 -0
- package/carbon/volumes/grafana/provisioning/datasources/datasources.yml +43 -0
- package/carbon/volumes/kong/docker-entrypoint.sh +9 -0
- package/carbon/volumes/kong/kong.yml +208 -0
- package/carbon/volumes/loki/loki-config.yml +58 -0
- package/carbon/volumes/n8n/hooks.js +131 -0
- package/carbon/volumes/n8n/scripts/setup.sh +66 -0
- package/carbon/volumes/prometheus/prometheus.yml +43 -0
- package/carbon/volumes/promtail/promtail-config.yml +64 -0
- package/carbon/volumes/traefik/middlewares.dev.yml +83 -0
- package/carbon/volumes/traefik/middlewares.yml +95 -0
- package/carbon/volumes/traefik/vite-dev.yml +20 -0
- package/package.json +95 -0
- package/src/access.js +354 -0
- package/src/activate.js +187 -0
- package/src/add.js +718 -0
- package/src/backup.js +786 -0
- package/src/cli.js +350 -0
- package/src/configure.js +967 -0
- package/src/console.js +155 -0
- package/src/create.js +1499 -0
- package/src/deploy.js +311 -0
- package/src/destroy.js +2033 -0
- package/src/diagnose.js +735 -0
- package/src/down.js +80 -0
- package/src/failover.js +1032 -0
- package/src/lib/backup-s3.js +179 -0
- package/src/lib/build.js +33 -0
- package/src/lib/checksum.js +28 -0
- package/src/lib/ci-setup.js +666 -0
- package/src/lib/cli/help.js +129 -0
- package/src/lib/cli/parse-flags.js +160 -0
- package/src/lib/cli/select-action.js +65 -0
- package/src/lib/cli/select-environment.js +108 -0
- package/src/lib/cli/tty-guard.js +75 -0
- package/src/lib/cloudflare.js +447 -0
- package/src/lib/colors.js +52 -0
- package/src/lib/command.js +361 -0
- package/src/lib/config.js +359 -0
- package/src/lib/cost.js +103 -0
- package/src/lib/deploy/bundle.js +231 -0
- package/src/lib/deploy/compose/acme-verify.js +121 -0
- package/src/lib/deploy/compose/build-args.js +78 -0
- package/src/lib/deploy/compose/ha.js +1874 -0
- package/src/lib/deploy/compose/index.js +1294 -0
- package/src/lib/deploy/compose/reconcile.js +74 -0
- package/src/lib/deploy/github.js +382 -0
- package/src/lib/deploy/image.js +191 -0
- package/src/lib/deploy/index.js +119 -0
- package/src/lib/deploy/k8s/LICENSE +99 -0
- package/src/lib/deploy/k8s/gitops-deploy.js +298 -0
- package/src/lib/deploy/k8s/ha/index.js +1000 -0
- package/src/lib/deploy/k8s/index.js +62 -0
- package/src/lib/deploy/k8s/k3s.js +2515 -0
- package/src/lib/deploy/orchestrator.js +1401 -0
- package/src/lib/deploy/prompts.js +545 -0
- package/src/lib/deploy/remote-build.js +130 -0
- package/src/lib/deploy/state.js +120 -0
- package/src/lib/deploy/utils.js +328 -0
- package/src/lib/deploy-logger.js +93 -0
- package/src/lib/dns-propagation.js +48 -0
- package/src/lib/environment.js +58 -0
- package/src/lib/fetch-retry.js +103 -0
- package/src/lib/github-environments.js +335 -0
- package/src/lib/hetzner-dns.js +377 -0
- package/src/lib/hetzner-guided-setup.js +275 -0
- package/src/lib/host-keys.js +37 -0
- package/src/lib/iac/cloud-init.js +52 -0
- package/src/lib/iac/index.js +325 -0
- package/src/lib/iac/programs/hetzner-compose.js +130 -0
- package/src/lib/iac/programs/hetzner-k8s.js +320 -0
- package/src/lib/kubectl.js +81 -0
- package/src/lib/licensing/index.js +259 -0
- package/src/lib/licensing/tiers.js +181 -0
- package/src/lib/licensing/validator.js +171 -0
- package/src/lib/merge-package-json.js +90 -0
- package/src/lib/operator-ip.js +381 -0
- package/src/lib/package-manager.js +111 -0
- package/src/lib/perf.js +71 -0
- package/src/lib/project-guard.js +39 -0
- package/src/lib/project.js +334 -0
- package/src/lib/providers/base.js +276 -0
- package/src/lib/providers/hetzner-s3.js +656 -0
- package/src/lib/providers/hetzner.js +755 -0
- package/src/lib/providers/index.js +164 -0
- package/src/lib/s3.js +510 -0
- package/src/lib/secret-scan.js +583 -0
- package/src/lib/secrets.js +63 -0
- package/src/lib/server-types.js +195 -0
- package/src/lib/shell.js +91 -0
- package/src/lib/ssh.js +241 -0
- package/src/lib/tracker.js +242 -0
- package/src/lib/upgrade-policy.js +170 -0
- package/src/lib/validators.js +105 -0
- package/src/lib/version.js +5 -0
- package/src/remove.js +292 -0
- package/src/reset.js +97 -0
- package/src/restore.js +871 -0
- package/src/scale.js +1734 -0
- package/src/shell.js +222 -0
- package/src/status.js +981 -0
- package/src/up.js +264 -0
- package/src/upgrade.js +721 -0
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
import { type ClassValue, clsx } from 'clsx';
|
|
2
|
+
import { AnimatePresence, motion } from 'framer-motion';
|
|
3
|
+
import { MoreHorizontal, Plus, Settings, Users } from 'lucide-react';
|
|
4
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { twMerge } from 'tailwind-merge';
|
|
7
|
+
import { SparkBurst } from './effects/SparkBurst';
|
|
8
|
+
import { Nav } from './Nav';
|
|
9
|
+
import { NewsletterSignup } from './NewsletterSignup';
|
|
10
|
+
|
|
11
|
+
function cn(...inputs: ClassValue[]) {
|
|
12
|
+
return twMerge(clsx(inputs));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Animation phases for the CLI
|
|
16
|
+
type Phase =
|
|
17
|
+
| 'typing'
|
|
18
|
+
| 'intro'
|
|
19
|
+
| 'projectName'
|
|
20
|
+
| 'packageManager'
|
|
21
|
+
| 'git'
|
|
22
|
+
| 'config'
|
|
23
|
+
| 'spinner'
|
|
24
|
+
| 'success'
|
|
25
|
+
| 'nextSteps'
|
|
26
|
+
| 'outro';
|
|
27
|
+
|
|
28
|
+
// Clack-style CLI animation
|
|
29
|
+
const TerminalWindow = ({ onComplete }: { onComplete: () => void }) => {
|
|
30
|
+
const [typed, setTyped] = useState('');
|
|
31
|
+
const [phase, setPhase] = useState<Phase>('typing');
|
|
32
|
+
const [spinnerStep, setSpinnerStep] = useState(0);
|
|
33
|
+
const [spinnerChar, setSpinnerChar] = useState(0);
|
|
34
|
+
const [selectedPm, setSelectedPm] = useState(0);
|
|
35
|
+
|
|
36
|
+
const command = 'npx vibecarbon create';
|
|
37
|
+
const spinnerChars = ['◒', '◐', '◓', '◑'];
|
|
38
|
+
const spinnerSteps = [
|
|
39
|
+
'Creating project structure',
|
|
40
|
+
'Generating configuration files',
|
|
41
|
+
'Generating Docker configuration',
|
|
42
|
+
'Generating backend files',
|
|
43
|
+
'Installing dependencies with pnpm',
|
|
44
|
+
'Initializing git repository',
|
|
45
|
+
];
|
|
46
|
+
const pmOptions = ['pnpm', 'npm', 'bun'];
|
|
47
|
+
|
|
48
|
+
// Typing animation
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (phase !== 'typing') return;
|
|
51
|
+
if (typed.length < command.length) {
|
|
52
|
+
const timeout = setTimeout(
|
|
53
|
+
() => {
|
|
54
|
+
setTyped(command.slice(0, typed.length + 1));
|
|
55
|
+
},
|
|
56
|
+
30 + Math.random() * 20
|
|
57
|
+
);
|
|
58
|
+
return () => clearTimeout(timeout);
|
|
59
|
+
}
|
|
60
|
+
const timeout = setTimeout(() => setPhase('intro'), 400);
|
|
61
|
+
return () => clearTimeout(timeout);
|
|
62
|
+
}, [typed, phase]);
|
|
63
|
+
|
|
64
|
+
// Phase progression
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (phase === 'intro') {
|
|
67
|
+
const timeout = setTimeout(() => setPhase('projectName'), 600);
|
|
68
|
+
return () => clearTimeout(timeout);
|
|
69
|
+
}
|
|
70
|
+
if (phase === 'projectName') {
|
|
71
|
+
const timeout = setTimeout(() => setPhase('packageManager'), 800);
|
|
72
|
+
return () => clearTimeout(timeout);
|
|
73
|
+
}
|
|
74
|
+
if (phase === 'packageManager') {
|
|
75
|
+
// Animate through package manager selection
|
|
76
|
+
const selectTimeout = setTimeout(() => {
|
|
77
|
+
setSelectedPm(0);
|
|
78
|
+
setTimeout(() => setPhase('git'), 400);
|
|
79
|
+
}, 600);
|
|
80
|
+
return () => clearTimeout(selectTimeout);
|
|
81
|
+
}
|
|
82
|
+
if (phase === 'git') {
|
|
83
|
+
const timeout = setTimeout(() => setPhase('config'), 600);
|
|
84
|
+
return () => clearTimeout(timeout);
|
|
85
|
+
}
|
|
86
|
+
if (phase === 'config') {
|
|
87
|
+
const timeout = setTimeout(() => setPhase('spinner'), 800);
|
|
88
|
+
return () => clearTimeout(timeout);
|
|
89
|
+
}
|
|
90
|
+
if (phase === 'spinner') {
|
|
91
|
+
if (spinnerStep < spinnerSteps.length) {
|
|
92
|
+
const timeout = setTimeout(() => {
|
|
93
|
+
setSpinnerStep((s) => s + 1);
|
|
94
|
+
}, 350);
|
|
95
|
+
return () => clearTimeout(timeout);
|
|
96
|
+
}
|
|
97
|
+
const timeout = setTimeout(() => setPhase('success'), 300);
|
|
98
|
+
return () => clearTimeout(timeout);
|
|
99
|
+
}
|
|
100
|
+
if (phase === 'success') {
|
|
101
|
+
const timeout = setTimeout(() => setPhase('nextSteps'), 400);
|
|
102
|
+
return () => clearTimeout(timeout);
|
|
103
|
+
}
|
|
104
|
+
if (phase === 'nextSteps') {
|
|
105
|
+
const timeout = setTimeout(() => setPhase('outro'), 600);
|
|
106
|
+
return () => clearTimeout(timeout);
|
|
107
|
+
}
|
|
108
|
+
if (phase === 'outro') {
|
|
109
|
+
const timeout = setTimeout(onComplete, 1200);
|
|
110
|
+
return () => clearTimeout(timeout);
|
|
111
|
+
}
|
|
112
|
+
}, [phase, spinnerStep, onComplete, spinnerSteps.length]);
|
|
113
|
+
|
|
114
|
+
// Spinner character animation
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (phase !== 'spinner' || spinnerStep >= spinnerSteps.length) return;
|
|
117
|
+
const interval = setInterval(() => {
|
|
118
|
+
setSpinnerChar((c) => (c + 1) % 4);
|
|
119
|
+
}, 80);
|
|
120
|
+
return () => clearInterval(interval);
|
|
121
|
+
}, [phase, spinnerStep, spinnerSteps.length]);
|
|
122
|
+
|
|
123
|
+
const getPhaseIndex = (p: Phase) => {
|
|
124
|
+
const phases: Phase[] = [
|
|
125
|
+
'typing',
|
|
126
|
+
'intro',
|
|
127
|
+
'projectName',
|
|
128
|
+
'packageManager',
|
|
129
|
+
'git',
|
|
130
|
+
'config',
|
|
131
|
+
'spinner',
|
|
132
|
+
'success',
|
|
133
|
+
'nextSteps',
|
|
134
|
+
'outro',
|
|
135
|
+
];
|
|
136
|
+
return phases.indexOf(p);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const isPastPhase = (p: Phase) => getPhaseIndex(phase) > getPhaseIndex(p);
|
|
140
|
+
const isAtOrPastPhase = (p: Phase) => getPhaseIndex(phase) >= getPhaseIndex(p);
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<div className="w-full h-full font-mono text-[13px] leading-relaxed p-4 flex flex-col overflow-hidden">
|
|
144
|
+
{/* Terminal Header */}
|
|
145
|
+
<div className="flex items-center justify-between mb-3 opacity-60">
|
|
146
|
+
<div className="flex space-x-2">
|
|
147
|
+
<div className="w-3 h-3 rounded-full bg-red-500/70" />
|
|
148
|
+
<div className="w-3 h-3 rounded-full bg-yellow-500/70" />
|
|
149
|
+
<div className="w-3 h-3 rounded-full bg-green-500/70" />
|
|
150
|
+
</div>
|
|
151
|
+
<div className="text-xs text-muted-foreground">Terminal</div>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
{/* CLI Content */}
|
|
155
|
+
<div className="flex-1 overflow-hidden">
|
|
156
|
+
{/* Command line */}
|
|
157
|
+
<div className="flex items-center text-foreground">
|
|
158
|
+
<span className="text-primary mr-2">$</span>
|
|
159
|
+
<span>{typed}</span>
|
|
160
|
+
{phase === 'typing' && typed.length < command.length && (
|
|
161
|
+
<span className="w-2 h-4 bg-primary ml-0.5 animate-pulse" />
|
|
162
|
+
)}
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
{/* Intro */}
|
|
166
|
+
{isAtOrPastPhase('intro') && (
|
|
167
|
+
<motion.div
|
|
168
|
+
initial={{ opacity: 0 }}
|
|
169
|
+
animate={{ opacity: 1 }}
|
|
170
|
+
className="mt-2 text-muted-foreground"
|
|
171
|
+
>
|
|
172
|
+
<span className="text-secondary-accent">◆</span> npx vibecarbon create
|
|
173
|
+
</motion.div>
|
|
174
|
+
)}
|
|
175
|
+
|
|
176
|
+
{/* Project name prompt */}
|
|
177
|
+
{isAtOrPastPhase('projectName') && (
|
|
178
|
+
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="mt-1">
|
|
179
|
+
<span className="text-muted-foreground/50">│</span>
|
|
180
|
+
<div className="ml-2 inline">
|
|
181
|
+
{isPastPhase('projectName') ? (
|
|
182
|
+
<>
|
|
183
|
+
<span className="text-primary">◇</span>{' '}
|
|
184
|
+
<span className="text-muted-foreground">What is your project name?</span>{' '}
|
|
185
|
+
<span className="text-foreground">my-app</span>
|
|
186
|
+
</>
|
|
187
|
+
) : (
|
|
188
|
+
<>
|
|
189
|
+
<span className="text-primary">◆</span>{' '}
|
|
190
|
+
<span className="text-foreground">What is your project name?</span>
|
|
191
|
+
<span className="ml-2 text-muted-foreground">my-app</span>
|
|
192
|
+
<span className="w-2 h-4 bg-primary ml-0.5 inline-block animate-pulse align-middle" />
|
|
193
|
+
</>
|
|
194
|
+
)}
|
|
195
|
+
</div>
|
|
196
|
+
</motion.div>
|
|
197
|
+
)}
|
|
198
|
+
|
|
199
|
+
{/* Package manager select */}
|
|
200
|
+
{isAtOrPastPhase('packageManager') && (
|
|
201
|
+
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="mt-1">
|
|
202
|
+
<span className="text-muted-foreground/50">│</span>
|
|
203
|
+
<div className="ml-2 inline">
|
|
204
|
+
{isPastPhase('packageManager') ? (
|
|
205
|
+
<>
|
|
206
|
+
<span className="text-primary">◇</span>{' '}
|
|
207
|
+
<span className="text-muted-foreground">Which package manager?</span>{' '}
|
|
208
|
+
<span className="text-foreground">pnpm</span>
|
|
209
|
+
</>
|
|
210
|
+
) : (
|
|
211
|
+
<>
|
|
212
|
+
<span className="text-primary">◆</span>{' '}
|
|
213
|
+
<span className="text-foreground">Which package manager?</span>
|
|
214
|
+
<div className="ml-4 mt-1 space-y-0.5">
|
|
215
|
+
{pmOptions.map((pm, i) => (
|
|
216
|
+
<div key={pm} className="flex items-center gap-2">
|
|
217
|
+
<span
|
|
218
|
+
className={i === selectedPm ? 'text-primary' : 'text-muted-foreground/50'}
|
|
219
|
+
>
|
|
220
|
+
{i === selectedPm ? '●' : '○'}
|
|
221
|
+
</span>
|
|
222
|
+
<span
|
|
223
|
+
className={i === selectedPm ? 'text-foreground' : 'text-muted-foreground'}
|
|
224
|
+
>
|
|
225
|
+
{pm}
|
|
226
|
+
</span>
|
|
227
|
+
{i === 0 && (
|
|
228
|
+
<span className="text-muted-foreground/50 text-xs">(recommended)</span>
|
|
229
|
+
)}
|
|
230
|
+
</div>
|
|
231
|
+
))}
|
|
232
|
+
</div>
|
|
233
|
+
</>
|
|
234
|
+
)}
|
|
235
|
+
</div>
|
|
236
|
+
</motion.div>
|
|
237
|
+
)}
|
|
238
|
+
|
|
239
|
+
{/* Git confirm */}
|
|
240
|
+
{isAtOrPastPhase('git') && (
|
|
241
|
+
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="mt-1">
|
|
242
|
+
<span className="text-muted-foreground/50">│</span>
|
|
243
|
+
<div className="ml-2 inline">
|
|
244
|
+
<span className="text-primary">◇</span>{' '}
|
|
245
|
+
<span className="text-muted-foreground">Initialize a git repository?</span>{' '}
|
|
246
|
+
<span className="text-foreground">Yes</span>
|
|
247
|
+
</div>
|
|
248
|
+
</motion.div>
|
|
249
|
+
)}
|
|
250
|
+
|
|
251
|
+
{/* Configuration box */}
|
|
252
|
+
{isAtOrPastPhase('config') && (
|
|
253
|
+
<motion.div
|
|
254
|
+
initial={{ opacity: 0 }}
|
|
255
|
+
animate={{ opacity: 1 }}
|
|
256
|
+
className="mt-2 border border-border/40 rounded px-3 py-2 bg-muted/50 dark:bg-white/[0.02] text-xs"
|
|
257
|
+
>
|
|
258
|
+
<div className="text-muted-foreground mb-1.5">Configuration</div>
|
|
259
|
+
<div className="space-y-0.5 text-foreground">
|
|
260
|
+
<div>
|
|
261
|
+
Project: <span className="text-muted-foreground">my-app</span>
|
|
262
|
+
</div>
|
|
263
|
+
<div>
|
|
264
|
+
Package manager: <span className="text-muted-foreground">pnpm</span>
|
|
265
|
+
</div>
|
|
266
|
+
<div>
|
|
267
|
+
Git: <span className="text-muted-foreground">yes</span>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
</motion.div>
|
|
271
|
+
)}
|
|
272
|
+
|
|
273
|
+
{/* Spinner steps */}
|
|
274
|
+
{isAtOrPastPhase('spinner') && (
|
|
275
|
+
<div className="mt-2 space-y-0.5">
|
|
276
|
+
{spinnerSteps.map((step, i) => {
|
|
277
|
+
if (i > spinnerStep) return null;
|
|
278
|
+
const isComplete = i < spinnerStep;
|
|
279
|
+
const isCurrent = i === spinnerStep && phase === 'spinner';
|
|
280
|
+
return (
|
|
281
|
+
<motion.div
|
|
282
|
+
key={step}
|
|
283
|
+
initial={{ opacity: 0 }}
|
|
284
|
+
animate={{ opacity: 1 }}
|
|
285
|
+
className="flex items-center gap-2"
|
|
286
|
+
>
|
|
287
|
+
{isComplete ? (
|
|
288
|
+
<span className="text-primary">✓</span>
|
|
289
|
+
) : isCurrent ? (
|
|
290
|
+
<span className="text-primary">{spinnerChars[spinnerChar]}</span>
|
|
291
|
+
) : (
|
|
292
|
+
<span className="text-primary">✓</span>
|
|
293
|
+
)}
|
|
294
|
+
<span className={isComplete ? 'text-muted-foreground' : 'text-foreground'}>
|
|
295
|
+
{step}
|
|
296
|
+
</span>
|
|
297
|
+
</motion.div>
|
|
298
|
+
);
|
|
299
|
+
})}
|
|
300
|
+
</div>
|
|
301
|
+
)}
|
|
302
|
+
|
|
303
|
+
{/* Success */}
|
|
304
|
+
{isAtOrPastPhase('success') && (
|
|
305
|
+
<motion.div
|
|
306
|
+
initial={{ opacity: 0 }}
|
|
307
|
+
animate={{ opacity: 1 }}
|
|
308
|
+
className="mt-2 text-primary"
|
|
309
|
+
>
|
|
310
|
+
<span>◇</span> Project created successfully
|
|
311
|
+
</motion.div>
|
|
312
|
+
)}
|
|
313
|
+
|
|
314
|
+
{/* Next steps */}
|
|
315
|
+
{isAtOrPastPhase('nextSteps') && (
|
|
316
|
+
<motion.div
|
|
317
|
+
initial={{ opacity: 0 }}
|
|
318
|
+
animate={{ opacity: 1 }}
|
|
319
|
+
className="mt-2 border border-border/40 rounded px-3 py-2 bg-muted/50 dark:bg-white/[0.02] text-xs"
|
|
320
|
+
>
|
|
321
|
+
<div className="text-muted-foreground mb-1">Next steps</div>
|
|
322
|
+
<div className="text-foreground space-y-0.5">
|
|
323
|
+
<div>cd my-app</div>
|
|
324
|
+
<div>pnpm dev:start</div>
|
|
325
|
+
</div>
|
|
326
|
+
</motion.div>
|
|
327
|
+
)}
|
|
328
|
+
|
|
329
|
+
{/* Outro */}
|
|
330
|
+
{isAtOrPastPhase('outro') && (
|
|
331
|
+
<motion.div
|
|
332
|
+
initial={{ opacity: 0 }}
|
|
333
|
+
animate={{ opacity: 1 }}
|
|
334
|
+
className="mt-2 text-muted-foreground"
|
|
335
|
+
>
|
|
336
|
+
<span className="text-secondary-accent">◇</span> Happy building!
|
|
337
|
+
</motion.div>
|
|
338
|
+
)}
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
);
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// SaaS Dashboard Preview
|
|
345
|
+
const DashboardPreview = () => {
|
|
346
|
+
const projects = [
|
|
347
|
+
{ name: 'my-app', env: 'production', status: 'healthy', users: '1.2k' },
|
|
348
|
+
{ name: 'staging', env: 'staging', status: 'healthy', users: '45' },
|
|
349
|
+
];
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<div className="w-full h-full flex text-xs">
|
|
353
|
+
{/* Sidebar */}
|
|
354
|
+
<div className="w-14 border-r border-border/50 flex flex-col items-center py-3 gap-3 bg-black/5 dark:bg-black/20">
|
|
355
|
+
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-primary to-primary/60 flex items-center justify-center text-background font-bold text-sm">
|
|
356
|
+
C
|
|
357
|
+
</div>
|
|
358
|
+
<div className="flex-1 flex flex-col gap-2 mt-2">
|
|
359
|
+
<div className="w-8 h-8 rounded-lg bg-primary/20 flex items-center justify-center text-primary">
|
|
360
|
+
<svg
|
|
361
|
+
className="w-4 h-4"
|
|
362
|
+
fill="none"
|
|
363
|
+
viewBox="0 0 24 24"
|
|
364
|
+
stroke="currentColor"
|
|
365
|
+
strokeWidth={2}
|
|
366
|
+
aria-hidden="true"
|
|
367
|
+
>
|
|
368
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M4 6h16M4 12h16M4 18h7" />
|
|
369
|
+
</svg>
|
|
370
|
+
</div>
|
|
371
|
+
<div className="w-8 h-8 rounded-lg hover:bg-black/5 dark:hover:bg-white/5 flex items-center justify-center text-muted-foreground">
|
|
372
|
+
<Users className="w-4 h-4" />
|
|
373
|
+
</div>
|
|
374
|
+
<div className="w-8 h-8 rounded-lg hover:bg-black/5 dark:hover:bg-white/5 flex items-center justify-center text-muted-foreground">
|
|
375
|
+
<Settings className="w-4 h-4" />
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
<div className="w-7 h-7 rounded-full bg-secondary-accent/20 flex items-center justify-center text-secondary-accent text-[10px] font-medium">
|
|
379
|
+
JD
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
|
|
383
|
+
{/* Main content */}
|
|
384
|
+
<div className="flex-1 p-4 overflow-hidden">
|
|
385
|
+
{/* Header */}
|
|
386
|
+
<div className="flex items-center justify-between mb-4">
|
|
387
|
+
<div>
|
|
388
|
+
<h2 className="text-foreground font-semibold text-sm">Projects</h2>
|
|
389
|
+
<p className="text-muted-foreground text-[10px]">Manage your deployments</p>
|
|
390
|
+
</div>
|
|
391
|
+
<button
|
|
392
|
+
type="button"
|
|
393
|
+
className="flex items-center gap-1 px-2.5 py-1.5 rounded-full bg-primary text-white text-[10px] font-medium"
|
|
394
|
+
>
|
|
395
|
+
<Plus className="w-3 h-3" />
|
|
396
|
+
New Project
|
|
397
|
+
</button>
|
|
398
|
+
</div>
|
|
399
|
+
|
|
400
|
+
{/* Projects list */}
|
|
401
|
+
<div className="space-y-2">
|
|
402
|
+
{projects.map((project) => (
|
|
403
|
+
<div
|
|
404
|
+
key={project.name}
|
|
405
|
+
className="flex items-center justify-between p-3 rounded-lg border border-border/50 bg-black/[0.02] dark:bg-white/[0.02] hover:bg-black/[0.04] dark:hover:bg-white/[0.04] transition-colors"
|
|
406
|
+
>
|
|
407
|
+
<div className="flex items-center gap-3">
|
|
408
|
+
<div className="w-8 h-8 rounded-md bg-gradient-to-br from-primary/20 to-secondary-accent/20 flex items-center justify-center text-foreground font-medium text-[10px]">
|
|
409
|
+
{project.name[0].toUpperCase()}
|
|
410
|
+
</div>
|
|
411
|
+
<div>
|
|
412
|
+
<div className="text-foreground font-medium text-xs">{project.name}</div>
|
|
413
|
+
<div className="text-muted-foreground text-[10px]">{project.env}</div>
|
|
414
|
+
</div>
|
|
415
|
+
</div>
|
|
416
|
+
<div className="flex items-center gap-4">
|
|
417
|
+
<div className="text-right">
|
|
418
|
+
<div className="flex items-center gap-1">
|
|
419
|
+
<div className="w-1.5 h-1.5 rounded-full bg-green-500" />
|
|
420
|
+
<span className="text-muted-foreground text-[10px]">{project.status}</span>
|
|
421
|
+
</div>
|
|
422
|
+
<div className="text-muted-foreground text-[10px]">{project.users} users</div>
|
|
423
|
+
</div>
|
|
424
|
+
<button
|
|
425
|
+
type="button"
|
|
426
|
+
className="p-1 hover:bg-black/10 dark:hover:bg-white/10 rounded"
|
|
427
|
+
>
|
|
428
|
+
<MoreHorizontal className="w-4 h-4 text-muted-foreground" />
|
|
429
|
+
</button>
|
|
430
|
+
</div>
|
|
431
|
+
</div>
|
|
432
|
+
))}
|
|
433
|
+
</div>
|
|
434
|
+
|
|
435
|
+
{/* Stats row */}
|
|
436
|
+
<div className="grid grid-cols-3 gap-2 mt-4">
|
|
437
|
+
{[
|
|
438
|
+
{ label: 'Total Users', value: '1,247', change: '+12%' },
|
|
439
|
+
{ label: 'API Calls', value: '847K', change: '+8%' },
|
|
440
|
+
{ label: 'Avg Latency', value: '24ms', change: '-5%' },
|
|
441
|
+
].map((stat) => (
|
|
442
|
+
<div
|
|
443
|
+
key={stat.label}
|
|
444
|
+
className="p-2.5 rounded-lg border border-border/50 bg-black/[0.02] dark:bg-white/[0.02]"
|
|
445
|
+
>
|
|
446
|
+
<div className="text-muted-foreground text-[10px]">{stat.label}</div>
|
|
447
|
+
<div className="text-foreground font-semibold text-sm">{stat.value}</div>
|
|
448
|
+
<div
|
|
449
|
+
className={`text-[10px] ${stat.change.startsWith('+') ? 'text-green-400' : 'text-primary'}`}
|
|
450
|
+
>
|
|
451
|
+
{stat.change}
|
|
452
|
+
</div>
|
|
453
|
+
</div>
|
|
454
|
+
))}
|
|
455
|
+
</div>
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
);
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
export default function Hero() {
|
|
462
|
+
const { t } = useTranslation();
|
|
463
|
+
const [terminalComplete, setTerminalComplete] = useState(false);
|
|
464
|
+
const [animationKey, setAnimationKey] = useState(0);
|
|
465
|
+
const [pillSparkTrigger, setPillSparkTrigger] = useState(0);
|
|
466
|
+
|
|
467
|
+
// Re-fire pill sparks every 10 seconds
|
|
468
|
+
useEffect(() => {
|
|
469
|
+
const interval = setInterval(() => setPillSparkTrigger((t) => t + 1), 10000);
|
|
470
|
+
return () => clearInterval(interval);
|
|
471
|
+
}, []);
|
|
472
|
+
|
|
473
|
+
const handleTerminalComplete = useCallback(() => {
|
|
474
|
+
setTerminalComplete(true);
|
|
475
|
+
}, []);
|
|
476
|
+
|
|
477
|
+
useEffect(() => {
|
|
478
|
+
if (terminalComplete) {
|
|
479
|
+
const timeout = setTimeout(() => {
|
|
480
|
+
setTerminalComplete(false);
|
|
481
|
+
setAnimationKey((k) => k + 1);
|
|
482
|
+
}, 6000);
|
|
483
|
+
return () => clearTimeout(timeout);
|
|
484
|
+
}
|
|
485
|
+
}, [terminalComplete]);
|
|
486
|
+
|
|
487
|
+
return (
|
|
488
|
+
<div className="relative min-h-screen w-full bg-background text-foreground overflow-x-hidden selection:bg-primary/30">
|
|
489
|
+
<Nav />
|
|
490
|
+
|
|
491
|
+
<main className="relative z-10 flex flex-col items-center justify-center min-h-screen px-4 pt-32 pb-32">
|
|
492
|
+
<div className="text-center max-w-4xl mx-auto mb-6 relative">
|
|
493
|
+
<motion.div
|
|
494
|
+
initial={{ opacity: 0 }}
|
|
495
|
+
animate={{ opacity: 1 }}
|
|
496
|
+
transition={{ duration: 0.8, delay: 0.3 }}
|
|
497
|
+
className="flex items-center justify-center gap-3 select-none cursor-default mb-6"
|
|
498
|
+
>
|
|
499
|
+
{/* biome-ignore lint/a11y/noStaticElementInteractions: decorative hover spark effect */}
|
|
500
|
+
<span
|
|
501
|
+
className="relative px-4 py-1.5 rounded-full text-sm font-medium overflow-visible group"
|
|
502
|
+
onMouseEnter={() => setPillSparkTrigger((t) => t + 1)}
|
|
503
|
+
>
|
|
504
|
+
{/* Animated gradient border */}
|
|
505
|
+
<span className="absolute inset-0 rounded-full bg-gradient-to-r from-violet-500 via-fuchsia-400 via-amber-300 to-violet-500 badge-shimmer" />
|
|
506
|
+
{/* Inner fill */}
|
|
507
|
+
<span className="absolute inset-[1.5px] rounded-full bg-gradient-to-r from-violet-950/90 via-fuchsia-950/80 to-violet-950/90 dark:from-violet-950/90 dark:via-fuchsia-950/80 dark:to-violet-950/90" />
|
|
508
|
+
{/* Shimmer highlight overlay */}
|
|
509
|
+
<span className="absolute inset-[1.5px] rounded-full bg-gradient-to-r from-transparent via-white/10 to-transparent badge-shimmer opacity-60" />
|
|
510
|
+
{/* Spark burst: fires on mount, every 10s, and on hover */}
|
|
511
|
+
<SparkBurst delay={1.1} palette="fuchsia" trigger={pillSparkTrigger} />
|
|
512
|
+
{/* Text content */}
|
|
513
|
+
<span className="relative z-10 bg-gradient-to-r from-violet-400 via-fuchsia-300 to-amber-300 dark:from-violet-300 dark:via-fuchsia-200 dark:to-amber-200 bg-clip-text text-transparent">
|
|
514
|
+
{t('landing.hero.badge')}
|
|
515
|
+
</span>
|
|
516
|
+
</span>
|
|
517
|
+
</motion.div>
|
|
518
|
+
|
|
519
|
+
<motion.h1
|
|
520
|
+
initial={{ opacity: 0, y: 20 }}
|
|
521
|
+
animate={{ opacity: 1, y: 0 }}
|
|
522
|
+
transition={{ duration: 0.8, delay: 0.1 }}
|
|
523
|
+
className="text-5xl md:text-7xl lg:text-8xl font-black tracking-tight mb-6"
|
|
524
|
+
>
|
|
525
|
+
<span className="block text-foreground">{t('landing.hero.headline1')}</span>
|
|
526
|
+
<span className="block text-primary pb-2">Vibecoding</span>
|
|
527
|
+
</motion.h1>
|
|
528
|
+
|
|
529
|
+
<motion.p
|
|
530
|
+
initial={{ opacity: 0 }}
|
|
531
|
+
animate={{ opacity: 1 }}
|
|
532
|
+
transition={{ duration: 0.8, delay: 0.2 }}
|
|
533
|
+
className="text-lg md:text-xl text-muted-foreground max-w-2xl mx-auto mb-6"
|
|
534
|
+
>
|
|
535
|
+
<span className="text-foreground/80">{t('landing.hero.subheading')}</span>
|
|
536
|
+
</motion.p>
|
|
537
|
+
</div>
|
|
538
|
+
|
|
539
|
+
<motion.div
|
|
540
|
+
initial={{ opacity: 0, y: 10 }}
|
|
541
|
+
animate={{ opacity: 1, y: 0 }}
|
|
542
|
+
transition={{ duration: 0.6, delay: 0.4 }}
|
|
543
|
+
className="mb-10"
|
|
544
|
+
>
|
|
545
|
+
<NewsletterSignup className="justify-center" buttonText={t('landing.hero.getUpdates')} />
|
|
546
|
+
</motion.div>
|
|
547
|
+
|
|
548
|
+
<div className="w-full max-w-3xl relative overflow-visible">
|
|
549
|
+
{/* Ambient glow orb — 3 layers for depth */}
|
|
550
|
+
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 -z-10 pointer-events-none">
|
|
551
|
+
<div className="hero-orb w-[700px] h-[400px] rounded-full bg-primary/8 blur-[100px]" />
|
|
552
|
+
<div className="absolute inset-[80px] rounded-full bg-primary/15 blur-[60px]" />
|
|
553
|
+
<div className="absolute inset-[160px] rounded-full bg-primary/30 blur-[30px]" />
|
|
554
|
+
<div className="absolute top-[40%] left-[20%] w-[200px] h-[200px] rounded-full bg-secondary-accent/10 blur-[80px]" />
|
|
555
|
+
</div>
|
|
556
|
+
|
|
557
|
+
<div className="h-[380px] relative">
|
|
558
|
+
<motion.div
|
|
559
|
+
initial={false}
|
|
560
|
+
className={cn(
|
|
561
|
+
'absolute inset-0 overflow-hidden border transition-colors duration-1000 rounded-2xl shadow-2xl backdrop-blur-md',
|
|
562
|
+
terminalComplete ? 'bg-card/70 border-border' : 'bg-background/65 border-border/50'
|
|
563
|
+
)}
|
|
564
|
+
>
|
|
565
|
+
{/* Glass spotlight from above */}
|
|
566
|
+
<div className="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-primary to-transparent z-20" />
|
|
567
|
+
<div
|
|
568
|
+
className="absolute inset-x-0 top-0 h-[220px] pointer-events-none z-10"
|
|
569
|
+
style={{
|
|
570
|
+
background:
|
|
571
|
+
'radial-gradient(ellipse 70% 55% at 50% 0%, oklch(0.82 0.14 192 / 0.30) 0%, transparent 100%)',
|
|
572
|
+
}}
|
|
573
|
+
/>
|
|
574
|
+
<div className="spotlight-particles absolute inset-0 pointer-events-none opacity-50 z-10" />
|
|
575
|
+
|
|
576
|
+
<div className="absolute inset-0 bg-gradient-to-br from-black/5 dark:from-white/5 to-transparent pointer-events-none z-20 rounded-2xl" />
|
|
577
|
+
|
|
578
|
+
<AnimatePresence mode="wait">
|
|
579
|
+
{!terminalComplete ? (
|
|
580
|
+
<motion.div
|
|
581
|
+
key={`terminal-${animationKey}`}
|
|
582
|
+
initial={{ opacity: 0 }}
|
|
583
|
+
animate={{ opacity: 1 }}
|
|
584
|
+
exit={{ opacity: 0, filter: 'blur(10px)' }}
|
|
585
|
+
transition={{ duration: 0.5 }}
|
|
586
|
+
className="h-full"
|
|
587
|
+
>
|
|
588
|
+
<TerminalWindow onComplete={handleTerminalComplete} />
|
|
589
|
+
</motion.div>
|
|
590
|
+
) : (
|
|
591
|
+
<motion.div
|
|
592
|
+
key={`dashboard-${animationKey}`}
|
|
593
|
+
initial={{ opacity: 0, scale: 0.95, filter: 'blur(10px)' }}
|
|
594
|
+
animate={{ opacity: 1, scale: 1, filter: 'blur(0px)' }}
|
|
595
|
+
transition={{ duration: 0.8 }}
|
|
596
|
+
className="h-full"
|
|
597
|
+
>
|
|
598
|
+
<DashboardPreview />
|
|
599
|
+
</motion.div>
|
|
600
|
+
)}
|
|
601
|
+
</AnimatePresence>
|
|
602
|
+
</motion.div>
|
|
603
|
+
</div>
|
|
604
|
+
</div>
|
|
605
|
+
</main>
|
|
606
|
+
</div>
|
|
607
|
+
);
|
|
608
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Eye, X } from 'lucide-react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { useAuth } from './auth/AuthProvider';
|
|
4
|
+
import { Button } from './ui/button';
|
|
5
|
+
|
|
6
|
+
export function ImpersonationBanner() {
|
|
7
|
+
const { t } = useTranslation();
|
|
8
|
+
const { isImpersonating, impersonationTarget, stopImpersonating } = useAuth();
|
|
9
|
+
|
|
10
|
+
if (!isImpersonating || !impersonationTarget) return null;
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="fixed top-0 inset-x-0 z-[100] bg-amber-500 text-black px-4 py-1.5 flex items-center justify-center gap-3 text-sm font-medium">
|
|
14
|
+
<Eye className="size-4 shrink-0" />
|
|
15
|
+
<span>
|
|
16
|
+
{t('admin.impersonating', {
|
|
17
|
+
email: impersonationTarget.email,
|
|
18
|
+
})}
|
|
19
|
+
</span>
|
|
20
|
+
<Button
|
|
21
|
+
variant="ghost"
|
|
22
|
+
size="xs"
|
|
23
|
+
className="h-6 px-2 text-black hover:bg-amber-600 hover:text-black"
|
|
24
|
+
onClick={stopImpersonating}
|
|
25
|
+
>
|
|
26
|
+
<X className="size-3 mr-1" />
|
|
27
|
+
{t('admin.stopImpersonating')}
|
|
28
|
+
</Button>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|