openship 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (531) hide show
  1. package/.env.example +19 -3
  2. package/CONTRIBUTING.md +69 -19
  3. package/LICENSE +661 -0
  4. package/README.md +304 -108
  5. package/apps/api/Dockerfile +6 -8
  6. package/apps/api/package.json +25 -21
  7. package/apps/api/src/app.ts +46 -3
  8. package/apps/api/src/config/env.ts +81 -7
  9. package/apps/api/src/lib/auth-mode.ts +42 -0
  10. package/apps/api/src/lib/auth.ts +105 -0
  11. package/apps/api/src/lib/cache.ts +128 -0
  12. package/apps/api/src/lib/cloud-auth-proxy.ts +310 -0
  13. package/apps/api/src/lib/cloud-client.ts +103 -0
  14. package/apps/api/src/lib/compose-parser.ts +138 -0
  15. package/apps/api/src/lib/controller-helpers.ts +90 -0
  16. package/apps/api/src/lib/email-templates.ts +94 -0
  17. package/apps/api/src/lib/encryption.ts +102 -0
  18. package/apps/api/src/lib/local-user.ts +65 -0
  19. package/apps/api/src/lib/mail.ts +48 -0
  20. package/apps/api/src/lib/notifications.ts +195 -0
  21. package/apps/api/src/lib/openship-cloud.ts +101 -0
  22. package/apps/api/src/lib/resources.ts +96 -0
  23. package/apps/api/src/lib/screenshots.ts +189 -0
  24. package/apps/api/src/lib/ssh-manager.ts +369 -0
  25. package/apps/api/src/lib/ssl-scheduler.ts +109 -0
  26. package/apps/api/src/lib/stack-detector.ts +708 -0
  27. package/apps/api/src/lib/system-debug.ts +10 -0
  28. package/apps/api/src/middleware/auth.ts +49 -13
  29. package/apps/api/src/middleware/error-handler.ts +23 -3
  30. package/apps/api/src/middleware/index.ts +2 -0
  31. package/apps/api/src/middleware/internal-auth.ts +40 -0
  32. package/apps/api/src/middleware/local-only.ts +20 -0
  33. package/apps/api/src/middleware/rate-limiter.ts +9 -0
  34. package/apps/api/src/modules/analytics/analytics.controller.ts +111 -0
  35. package/apps/api/src/modules/analytics/analytics.routes.ts +29 -0
  36. package/apps/api/src/modules/analytics/analytics.schema.ts +37 -0
  37. package/apps/api/src/modules/analytics/analytics.service.ts +264 -0
  38. package/apps/api/src/modules/auth/auth.routes.ts +233 -6
  39. package/apps/api/src/modules/billing/billing.routes.ts +8 -0
  40. package/apps/api/src/modules/cloud/cloud.controller.ts +225 -0
  41. package/apps/api/src/modules/cloud/cloud.routes.ts +40 -0
  42. package/apps/api/src/modules/deployments/build.service.ts +791 -0
  43. package/apps/api/src/modules/deployments/deployment-lifecycle.ts +138 -0
  44. package/apps/api/src/modules/deployments/deployment.controller.ts +324 -16
  45. package/apps/api/src/modules/deployments/deployment.routes.ts +33 -7
  46. package/apps/api/src/modules/deployments/deployment.schema.ts +37 -5
  47. package/apps/api/src/modules/deployments/deployment.service.ts +178 -11
  48. package/apps/api/src/modules/deployments/preflight.ts +280 -0
  49. package/apps/api/src/modules/deployments/prepare.service.ts +252 -0
  50. package/apps/api/src/modules/deployments/session-manager.ts +283 -0
  51. package/apps/api/src/modules/deployments/ssl.service.ts +73 -0
  52. package/apps/api/src/modules/domains/domain.controller.ts +59 -7
  53. package/apps/api/src/modules/domains/domain.routes.ts +20 -5
  54. package/apps/api/src/modules/domains/domain.schema.ts +35 -0
  55. package/apps/api/src/modules/domains/domain.service.ts +375 -9
  56. package/apps/api/src/modules/github/github.auth.ts +397 -0
  57. package/apps/api/src/modules/github/github.controller.ts +387 -0
  58. package/apps/api/src/modules/github/github.local-auth.ts +251 -0
  59. package/apps/api/src/modules/github/github.routes.ts +50 -0
  60. package/apps/api/src/modules/github/github.schema.ts +52 -0
  61. package/apps/api/src/modules/github/github.service.ts +592 -0
  62. package/apps/api/src/modules/github/github.types.ts +158 -0
  63. package/apps/api/src/modules/github/github.webhook.ts +186 -0
  64. package/apps/api/src/modules/github/index.ts +16 -0
  65. package/apps/api/src/modules/health/health.routes.ts +35 -0
  66. package/apps/api/src/modules/projects/project.controller.ts +481 -12
  67. package/apps/api/src/modules/projects/project.routes.ts +63 -6
  68. package/apps/api/src/modules/projects/project.schema.ts +104 -6
  69. package/apps/api/src/modules/projects/project.service.ts +502 -11
  70. package/apps/api/src/modules/settings/settings.controller.ts +55 -0
  71. package/apps/api/src/modules/settings/settings.routes.ts +25 -0
  72. package/apps/api/src/modules/settings/settings.service.ts +49 -0
  73. package/apps/api/src/modules/system/filesystem.controller.ts +66 -0
  74. package/apps/api/src/modules/system/index.ts +1 -0
  75. package/apps/api/src/modules/system/server-check.controller.ts +475 -0
  76. package/apps/api/src/modules/system/setup-session.ts +255 -0
  77. package/apps/api/src/modules/system/setup.controller.ts +128 -0
  78. package/apps/api/src/modules/system/system.routes.ts +42 -0
  79. package/apps/api/src/modules/webhooks/webhook.controller.ts +57 -15
  80. package/apps/api/src/modules/webhooks/webhook.routes.ts +11 -8
  81. package/apps/api/src/modules/webhooks/webhook.service.ts +46 -5
  82. package/apps/api/src/modules/webhooks/webhook.types.ts +35 -0
  83. package/apps/api/tsconfig.json +3 -1
  84. package/apps/cli/package.json +8 -10
  85. package/apps/cli/tsconfig.json +2 -1
  86. package/apps/dashboard/Dockerfile +4 -6
  87. package/apps/dashboard/components.json +20 -0
  88. package/apps/dashboard/next-env.d.ts +6 -0
  89. package/apps/dashboard/next.config.mjs +11 -1
  90. package/apps/dashboard/package.json +25 -13
  91. package/apps/dashboard/postcss.config.mjs +5 -0
  92. package/apps/dashboard/public/android-chrome-192x192.png +0 -0
  93. package/apps/dashboard/public/android-chrome-512x512.png +0 -0
  94. package/apps/dashboard/public/apple-touch-icon.png +0 -0
  95. package/apps/dashboard/public/favicon-16x16.png +0 -0
  96. package/apps/dashboard/public/favicon-32x32.png +0 -0
  97. package/apps/dashboard/public/favicon.ico +0 -0
  98. package/apps/dashboard/public/site.webmanifest +19 -0
  99. package/apps/dashboard/src/app/(auth)/forgot-password/page.tsx +114 -0
  100. package/apps/dashboard/src/app/(auth)/layout.tsx +18 -3
  101. package/apps/dashboard/src/app/(auth)/login/page.tsx +215 -6
  102. package/apps/dashboard/src/app/(auth)/providers.tsx +34 -0
  103. package/apps/dashboard/src/app/(auth)/register/page.tsx +171 -5
  104. package/apps/dashboard/src/app/(auth)/reset-password/page.tsx +172 -0
  105. package/apps/dashboard/src/app/(auth)/verify-email/page.tsx +190 -0
  106. package/apps/dashboard/src/app/(dashboard)/(deployment)/build/[id]/page.tsx +162 -0
  107. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/BuildSummary.tsx +166 -0
  108. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/DnsConfiguration.tsx +119 -0
  109. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/DomainSettings.tsx +291 -0
  110. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/Header.tsx +71 -0
  111. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/Sidebar.tsx +270 -0
  112. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/SkeletonLoader.tsx +87 -0
  113. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/page.tsx +169 -0
  114. package/apps/dashboard/src/app/(dashboard)/(deployment)/layout.tsx +7 -0
  115. package/apps/dashboard/src/app/(dashboard)/billing/page.tsx +1 -8
  116. package/apps/dashboard/src/app/(dashboard)/deployments/components/CommitDetailsModal.tsx +240 -0
  117. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentCard.tsx +121 -0
  118. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentHeader.tsx +89 -0
  119. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentMenu.tsx +182 -0
  120. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentsContent.tsx +324 -0
  121. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentsFilters.tsx +99 -0
  122. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentsList.tsx +31 -0
  123. package/apps/dashboard/src/app/(dashboard)/deployments/components/EmptyState.tsx +101 -0
  124. package/apps/dashboard/src/app/(dashboard)/deployments/components/LoadingSkeleton.tsx +57 -0
  125. package/apps/dashboard/src/app/(dashboard)/deployments/components/ProjectFilter.tsx +109 -0
  126. package/apps/dashboard/src/app/(dashboard)/deployments/components/index.ts +9 -0
  127. package/apps/dashboard/src/app/(dashboard)/deployments/page.tsx +9 -3
  128. package/apps/dashboard/src/app/(dashboard)/deployments/types.ts +44 -0
  129. package/apps/dashboard/src/app/(dashboard)/deployments/utils.ts +176 -0
  130. package/apps/dashboard/src/app/(dashboard)/domains/page.tsx +1 -6
  131. package/apps/dashboard/src/app/(dashboard)/integrations/page.tsx +321 -0
  132. package/apps/dashboard/src/app/(dashboard)/layout.tsx +22 -32
  133. package/apps/dashboard/src/app/(dashboard)/library/components/ConnectPrompt.tsx +187 -0
  134. package/apps/dashboard/src/app/(dashboard)/library/components/LibrarySidebar.tsx +147 -0
  135. package/apps/dashboard/src/app/(dashboard)/library/components/LoadingSkeleton.tsx +39 -0
  136. package/apps/dashboard/src/app/(dashboard)/library/components/LocalProjects.tsx +548 -0
  137. package/apps/dashboard/src/app/(dashboard)/library/components/RepositoryList.tsx +292 -0
  138. package/apps/dashboard/src/app/(dashboard)/library/components/TemplateGrid.tsx +36 -0
  139. package/apps/dashboard/src/app/(dashboard)/library/components/UrlImport.tsx +74 -0
  140. package/apps/dashboard/src/app/(dashboard)/library/page.tsx +121 -0
  141. package/apps/dashboard/src/app/(dashboard)/library/types.ts +26 -0
  142. package/apps/dashboard/src/app/(dashboard)/monitoring/page.tsx +1 -6
  143. package/apps/dashboard/src/app/(dashboard)/page.tsx +485 -0
  144. package/apps/dashboard/src/app/(dashboard)/projects/[id]/[[...slug]]/layout.tsx +22 -0
  145. package/apps/dashboard/src/app/(dashboard)/projects/[id]/[[...slug]]/page.tsx +286 -0
  146. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/AdvancedSettings.tsx +235 -0
  147. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/BuildSettings.tsx +174 -0
  148. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/DeleteConfirmationDialog.tsx +45 -0
  149. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/DeletionModal.tsx +95 -0
  150. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/Deployments.tsx +11 -0
  151. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/DomainSettings.tsx +523 -0
  152. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/EnvironmentSettings.tsx +246 -0
  153. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/GeneralSettings.tsx +93 -0
  154. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/GitInfo.tsx +261 -0
  155. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/GitSettings.tsx +391 -0
  156. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/LogsSettings.tsx +133 -0
  157. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/ProjectNotFound.tsx +95 -0
  158. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/ProjectsBottomNavigation.tsx +62 -0
  159. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/ResourceSettings.tsx +333 -0
  160. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/SettingSection.tsx +66 -0
  161. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/SettingsNavigation.tsx +27 -0
  162. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/SleepModeSettings.tsx +134 -0
  163. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/ProductionUrl.tsx +25 -0
  164. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/ProjectIdentity.tsx +159 -0
  165. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/ProjectInfo.tsx +58 -0
  166. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/SkeletonLoader.tsx +170 -0
  167. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/StatsCards.tsx +33 -0
  168. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/TopPaths.tsx +56 -0
  169. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/TrafficChart.tsx +157 -0
  170. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/index.ts +14 -0
  171. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/LogsActions.tsx +52 -0
  172. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/ServerLogs.tsx +249 -0
  173. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/TerminalLogs.tsx +682 -0
  174. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/logs.css +127 -0
  175. package/apps/dashboard/src/app/(dashboard)/projects/components/DeploymentCard.tsx +73 -0
  176. package/apps/dashboard/src/app/(dashboard)/projects/components/ProjectCard.tsx +330 -0
  177. package/apps/dashboard/src/app/(dashboard)/projects/page.tsx +326 -4
  178. package/apps/dashboard/src/app/(dashboard)/providers.tsx +23 -0
  179. package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/_components/components-tab.tsx +329 -0
  180. package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/_components/overview-tab.tsx +229 -0
  181. package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/_components/terminal-tab.tsx +38 -0
  182. package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/page.tsx +437 -0
  183. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/checking-state.tsx +15 -0
  184. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/choose-mode.tsx +54 -0
  185. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/component-row.tsx +65 -0
  186. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/error-banner.tsx +13 -0
  187. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/installing-panel.tsx +227 -0
  188. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/results-panel.tsx +90 -0
  189. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/setup-header.tsx +56 -0
  190. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/types.ts +13 -0
  191. package/apps/dashboard/src/app/(dashboard)/servers/new/page.tsx +666 -0
  192. package/apps/dashboard/src/app/(dashboard)/servers/page.tsx +283 -0
  193. package/apps/dashboard/src/app/(dashboard)/settings/_components/BuildPreferences.tsx +110 -0
  194. package/apps/dashboard/src/app/(dashboard)/settings/_components/CloudConnection.tsx +147 -0
  195. package/apps/dashboard/src/app/(dashboard)/settings/_components/InstanceInfo.tsx +54 -0
  196. package/apps/dashboard/src/app/(dashboard)/settings/_components/ServerConnection.tsx +407 -0
  197. package/apps/dashboard/src/app/(dashboard)/settings/_components/SettingsSection.tsx +32 -0
  198. package/apps/dashboard/src/app/(dashboard)/settings/page.tsx +62 -3
  199. package/apps/dashboard/src/app/auth/callback/close/page.tsx +23 -0
  200. package/apps/dashboard/src/app/auth/callback/install/page.tsx +40 -0
  201. package/apps/dashboard/src/app/authorize/page.tsx +137 -0
  202. package/apps/dashboard/src/app/globals.css +57 -3
  203. package/apps/dashboard/src/app/layout.tsx +43 -3
  204. package/apps/dashboard/src/components/auth-shell.tsx +66 -0
  205. package/apps/dashboard/src/components/deployments/types.ts +44 -0
  206. package/apps/dashboard/src/components/global-deployments/index.ts +17 -0
  207. package/apps/dashboard/src/components/i18n-provider.tsx +100 -0
  208. package/apps/dashboard/src/components/import-project/BuildSettings.tsx +376 -0
  209. package/apps/dashboard/src/components/import-project/BuildSkeleton.tsx +161 -0
  210. package/apps/dashboard/src/components/import-project/BuildTerminal.tsx +358 -0
  211. package/apps/dashboard/src/components/import-project/ComposeServices.tsx +455 -0
  212. package/apps/dashboard/src/components/import-project/DeploymentProcessing.tsx +510 -0
  213. package/apps/dashboard/src/components/import-project/DockerSettings.tsx +64 -0
  214. package/apps/dashboard/src/components/import-project/EnvironmentVariables.tsx +481 -0
  215. package/apps/dashboard/src/components/import-project/Frameworks.tsx +90 -0
  216. package/apps/dashboard/src/components/import-project/ProjectSettings.tsx +160 -0
  217. package/apps/dashboard/src/components/import-project/types.ts +82 -0
  218. package/apps/dashboard/src/components/integrations/GithubPermissionModal.tsx +173 -0
  219. package/apps/dashboard/src/components/language-switcher.tsx +36 -0
  220. package/apps/dashboard/src/components/logo.tsx +13 -0
  221. package/apps/dashboard/src/components/network-error-handler.tsx +27 -0
  222. package/apps/dashboard/src/components/oauth-buttons.tsx +81 -0
  223. package/apps/dashboard/src/components/overview/ActivityChart.tsx +247 -0
  224. package/apps/dashboard/src/components/overview/ApiRequestsCard.tsx +111 -0
  225. package/apps/dashboard/src/components/overview/CreditsCard.tsx +137 -0
  226. package/apps/dashboard/src/components/overview/DeploymentsChart.tsx +195 -0
  227. package/apps/dashboard/src/components/overview/EmptyState.tsx +126 -0
  228. package/apps/dashboard/src/components/overview/LoadingSkeleton.tsx +92 -0
  229. package/apps/dashboard/src/components/overview/OverviewHeader.tsx +74 -0
  230. package/apps/dashboard/src/components/overview/OverviewHeroChart.tsx +344 -0
  231. package/apps/dashboard/src/components/overview/ProjectsCard.tsx +134 -0
  232. package/apps/dashboard/src/components/overview/QuickLinks.tsx +119 -0
  233. package/apps/dashboard/src/components/overview/SandboxesCard.tsx +150 -0
  234. package/apps/dashboard/src/components/overview/StatsGrid.tsx +166 -0
  235. package/apps/dashboard/src/components/overview/TokenUsageChart.tsx +206 -0
  236. package/apps/dashboard/src/components/overview/UsageStats.tsx +79 -0
  237. package/apps/dashboard/src/components/overview/index.ts +30 -0
  238. package/apps/dashboard/src/components/overview/types.ts +116 -0
  239. package/apps/dashboard/src/components/page-header.tsx +25 -0
  240. package/apps/dashboard/src/components/project-settings/ServerSideSwitch.jsx +60 -0
  241. package/apps/dashboard/src/components/shared/AlertBox.tsx +39 -0
  242. package/apps/dashboard/src/components/shared/CTASection.tsx +45 -0
  243. package/apps/dashboard/src/components/shared/ErrorState.tsx +176 -0
  244. package/apps/dashboard/src/components/shared/FeatureCard.tsx +20 -0
  245. package/apps/dashboard/src/components/shared/MDXComponents.tsx +24 -0
  246. package/apps/dashboard/src/components/shared/MachineSettingsModal.tsx +286 -0
  247. package/apps/dashboard/src/components/shared/Modal.tsx +39 -0
  248. package/apps/dashboard/src/components/shared/OTPInput.tsx +122 -0
  249. package/apps/dashboard/src/components/shared/PageHero.tsx +21 -0
  250. package/apps/dashboard/src/components/shared/PlatformFeatureCard.tsx +27 -0
  251. package/apps/dashboard/src/components/shared/StatCard.tsx +14 -0
  252. package/apps/dashboard/src/components/shared/ValueCard.tsx +20 -0
  253. package/apps/dashboard/src/components/shared/index.ts +8 -0
  254. package/apps/dashboard/src/components/sidebar.tsx +282 -0
  255. package/apps/dashboard/src/components/theme-provider.tsx +96 -0
  256. package/apps/dashboard/src/components/toast.tsx +106 -0
  257. package/apps/dashboard/src/components/ui/CustomCursor.tsx +107 -0
  258. package/apps/dashboard/src/components/ui/CustomSelect.tsx +126 -0
  259. package/apps/dashboard/src/components/ui/DropdownMenu.tsx +174 -0
  260. package/apps/dashboard/src/components/ui/FileIcon.jsx +59 -0
  261. package/apps/dashboard/src/components/ui/IconPickerModal.tsx +273 -0
  262. package/apps/dashboard/src/components/ui/InfoBanner.jsx +39 -0
  263. package/apps/dashboard/src/components/ui/Logo.tsx +14 -0
  264. package/apps/dashboard/src/components/ui/Modal.tsx +117 -0
  265. package/apps/dashboard/src/components/ui/PageContainer.tsx +12 -0
  266. package/apps/dashboard/src/components/ui/SectionContainer.jsx +7 -0
  267. package/apps/dashboard/src/components/ui/SlidingToggle.tsx +128 -0
  268. package/apps/dashboard/src/components/ui/Toast.tsx +29 -0
  269. package/apps/dashboard/src/components/ui/button.tsx +58 -0
  270. package/apps/dashboard/src/components/ui/card.tsx +60 -0
  271. package/apps/dashboard/src/components/ui/input.tsx +20 -0
  272. package/apps/dashboard/src/components/ui/label.tsx +19 -0
  273. package/apps/dashboard/src/components/ui/select.tsx +10 -0
  274. package/apps/dashboard/src/components/ui/textarea.tsx +9 -0
  275. package/apps/dashboard/src/constants/lang-colors.ts +25 -0
  276. package/apps/dashboard/src/constants/mock.ts +38 -0
  277. package/apps/dashboard/src/context/AuthContext.tsx +91 -0
  278. package/apps/dashboard/src/context/CloudContext.tsx +275 -0
  279. package/apps/dashboard/src/context/DeploymentContext.tsx +71 -0
  280. package/apps/dashboard/src/context/GitHubContext.tsx +288 -0
  281. package/apps/dashboard/src/context/ModalContext.tsx +300 -0
  282. package/apps/dashboard/src/context/NetworkContext.tsx +32 -0
  283. package/apps/dashboard/src/context/ProjectSettingsContext.tsx +727 -0
  284. package/apps/dashboard/src/context/ToastContext.tsx +129 -0
  285. package/apps/dashboard/src/context/deployment/index.ts +3 -0
  286. package/apps/dashboard/src/context/deployment/types.ts +183 -0
  287. package/apps/dashboard/src/context/deployment/useDeploymentBuild.ts +547 -0
  288. package/apps/dashboard/src/context/deployment/useDeploymentConfig.ts +176 -0
  289. package/apps/dashboard/src/hooks/useBuildConnection.ts +334 -0
  290. package/apps/dashboard/src/hooks/useMonitorStream.ts +146 -0
  291. package/apps/dashboard/src/hooks/useProjectData.ts +110 -0
  292. package/apps/dashboard/src/hooks/useSSEConnection.ts +465 -0
  293. package/apps/dashboard/src/hooks/useSSEStream.ts +309 -0
  294. package/apps/dashboard/src/hooks/useSessions.ts +104 -0
  295. package/apps/dashboard/src/hooks/useSetupStream.ts +223 -0
  296. package/apps/dashboard/src/i18n/dictionaries/ar.json +218 -0
  297. package/apps/dashboard/src/i18n/dictionaries/en.json +218 -0
  298. package/apps/dashboard/src/i18n/index.ts +16 -0
  299. package/apps/dashboard/src/lib/api/ai.ts +22 -0
  300. package/apps/dashboard/src/lib/api/auth.ts +13 -0
  301. package/apps/dashboard/src/lib/api/client.ts +199 -0
  302. package/apps/dashboard/src/lib/api/cloud.ts +15 -0
  303. package/apps/dashboard/src/lib/api/deploy.ts +58 -0
  304. package/apps/dashboard/src/lib/api/domains.ts +13 -0
  305. package/apps/dashboard/src/lib/api/endpoints.ts +129 -0
  306. package/apps/dashboard/src/lib/api/github.ts +31 -0
  307. package/apps/dashboard/src/lib/api/icons.ts +12 -0
  308. package/apps/dashboard/src/lib/api/index.ts +41 -0
  309. package/apps/dashboard/src/lib/api/projects.ts +153 -0
  310. package/apps/dashboard/src/lib/api/sandbox.ts +14 -0
  311. package/apps/dashboard/src/lib/api/settings.ts +21 -0
  312. package/apps/dashboard/src/lib/api/system.ts +159 -0
  313. package/apps/dashboard/src/lib/auth-client.ts +54 -0
  314. package/apps/dashboard/src/lib/country.ts +25 -0
  315. package/apps/dashboard/src/lib/server/api.ts +140 -0
  316. package/apps/dashboard/src/lib/server/session.ts +103 -0
  317. package/apps/dashboard/src/lib/sseClient.ts +241 -0
  318. package/apps/dashboard/src/lib/sseMessageProcessors.ts +323 -0
  319. package/apps/dashboard/src/lib/utils.ts +6 -0
  320. package/apps/dashboard/src/proxy.ts +46 -0
  321. package/apps/dashboard/src/styles/Toast.module.css +92 -0
  322. package/apps/dashboard/src/styles/fonts.css +93 -0
  323. package/apps/dashboard/src/styles/theme.css +336 -0
  324. package/apps/dashboard/src/types/research.ts +42 -0
  325. package/apps/dashboard/src/utils/SSEParser.js +150 -0
  326. package/apps/dashboard/src/utils/authWindow.ts +143 -0
  327. package/apps/dashboard/src/utils/codeTheme.ts +127 -0
  328. package/apps/dashboard/src/utils/colors.json +2810 -0
  329. package/apps/dashboard/src/utils/date.ts +121 -0
  330. package/apps/dashboard/src/utils/deployment.ts +128 -0
  331. package/apps/dashboard/src/utils/deploymentPhaseDetector.ts +232 -0
  332. package/apps/dashboard/src/utils/extToLang.js +103 -0
  333. package/apps/dashboard/src/utils/github.ts +62 -0
  334. package/apps/dashboard/src/utils/icons.js +46 -0
  335. package/apps/dashboard/src/utils/repoSlug.ts +110 -0
  336. package/apps/dashboard/src/utils/research-api.ts +163 -0
  337. package/apps/dashboard/src/utils/suggestions.ts +84 -0
  338. package/apps/dashboard/src/utils/theme.js +1 -0
  339. package/apps/dashboard/src/utils/ui-helpers.tsx +30 -0
  340. package/apps/dashboard/src/utils/upload.js +12 -0
  341. package/apps/dashboard/tsconfig.json +27 -5
  342. package/apps/desktop/forge.config.json +36 -0
  343. package/apps/desktop/package.json +26 -0
  344. package/apps/desktop/scripts/dev.mjs +58 -0
  345. package/apps/desktop/src/main/index.ts +675 -0
  346. package/apps/desktop/src/main/types/desktop.d.ts +33 -0
  347. package/apps/desktop/src/preload/index.ts +88 -0
  348. package/apps/desktop/src/renderer/index.html +462 -0
  349. package/apps/desktop/src/renderer/onboarding.js +511 -0
  350. package/apps/desktop/src/renderer/styles.css +1139 -0
  351. package/apps/desktop/tsconfig.json +17 -0
  352. package/apps/web/.source/browser.ts +13 -0
  353. package/apps/web/.source/dynamic.ts +8 -0
  354. package/apps/web/.source/server.ts +22 -0
  355. package/apps/web/.source/source.config.mjs +20 -0
  356. package/apps/web/Dockerfile +4 -6
  357. package/apps/web/content/blog/how-ai-builds-work.mdx +55 -0
  358. package/apps/web/content/blog/introducing-openship.mdx +46 -0
  359. package/apps/web/content/blog/self-hosting-cost-breakdown.mdx +59 -0
  360. package/apps/web/content/docs/_meta.json +14 -0
  361. package/apps/web/content/docs/api.mdx +98 -0
  362. package/apps/web/content/docs/cli.mdx +80 -0
  363. package/apps/web/content/docs/first-deployment.mdx +56 -0
  364. package/apps/web/content/docs/index.mdx +27 -0
  365. package/apps/web/content/docs/installation.mdx +65 -0
  366. package/apps/web/content/docs/quickstart.mdx +45 -0
  367. package/apps/web/next-env.d.ts +6 -0
  368. package/apps/web/next.config.mjs +16 -2
  369. package/apps/web/package.json +19 -12
  370. package/apps/web/postcss.config.mjs +5 -0
  371. package/apps/web/public/android-chrome-192x192.png +0 -0
  372. package/apps/web/public/android-chrome-512x512.png +0 -0
  373. package/apps/web/public/apple-touch-icon.png +0 -0
  374. package/apps/web/public/favicon-16x16.png +0 -0
  375. package/apps/web/public/favicon-32x32.png +0 -0
  376. package/apps/web/public/favicon.ico +0 -0
  377. package/apps/web/public/site.webmanifest +1 -0
  378. package/apps/web/source.config.ts +17 -0
  379. package/apps/web/src/app/(marketing)/layout.tsx +7 -22
  380. package/apps/web/src/app/blog/[...slug]/page.tsx +247 -0
  381. package/apps/web/src/app/blog/blog.css +575 -0
  382. package/apps/web/src/app/blog/layout.tsx +50 -0
  383. package/apps/web/src/app/blog/page.tsx +235 -0
  384. package/apps/web/src/app/docs/[[...slug]]/page.tsx +67 -0
  385. package/apps/web/src/app/docs/layout.tsx +62 -0
  386. package/apps/web/src/app/docs/toc-theme.css +71 -0
  387. package/apps/web/src/app/download/page.tsx +396 -0
  388. package/apps/web/src/app/globals.css +20 -3
  389. package/apps/web/src/app/layout.tsx +57 -3
  390. package/apps/web/src/app/page.tsx +47 -6
  391. package/apps/web/src/app/robots.ts +15 -0
  392. package/apps/web/src/app/sitemap.ts +35 -0
  393. package/apps/web/src/components/blog/share-sidebar.tsx +81 -0
  394. package/apps/web/src/components/blog/writer-sidebar.tsx +81 -0
  395. package/apps/web/src/components/landing/ai-native.tsx +169 -0
  396. package/apps/web/src/components/landing/backups.tsx +139 -0
  397. package/apps/web/src/components/landing/cdn.tsx +109 -0
  398. package/apps/web/src/components/landing/coming-soon.tsx +180 -0
  399. package/apps/web/src/components/landing/dark-section.tsx +166 -0
  400. package/apps/web/src/components/landing/dashboard.tsx +407 -0
  401. package/apps/web/src/components/landing/developer-experience.tsx +224 -0
  402. package/apps/web/src/components/landing/dns-domains.tsx +146 -0
  403. package/apps/web/src/components/landing/download-button.tsx +151 -0
  404. package/apps/web/src/components/landing/features/ai-visual.tsx +48 -0
  405. package/apps/web/src/components/landing/features/cloud-visual.tsx +44 -0
  406. package/apps/web/src/components/landing/features/git-visual.tsx +61 -0
  407. package/apps/web/src/components/landing/features/index.tsx +642 -0
  408. package/apps/web/src/components/landing/features/infra-visual.tsx +69 -0
  409. package/apps/web/src/components/landing/features/rollback-visual.tsx +42 -0
  410. package/apps/web/src/components/landing/features/ssl-visual.tsx +41 -0
  411. package/apps/web/src/components/landing/features/stacks-visual.tsx +54 -0
  412. package/apps/web/src/components/landing/final-cta.tsx +116 -0
  413. package/apps/web/src/components/landing/footer.tsx +145 -0
  414. package/apps/web/src/components/landing/hero.tsx +110 -0
  415. package/apps/web/src/components/landing/how-it-works.tsx +210 -0
  416. package/apps/web/src/components/landing/index.ts +21 -0
  417. package/apps/web/src/components/landing/mail-server.tsx +122 -0
  418. package/apps/web/src/components/landing/navbar.tsx +199 -0
  419. package/apps/web/src/components/landing/no-lockin.tsx +185 -0
  420. package/apps/web/src/components/landing/open-source-cta.tsx +151 -0
  421. package/apps/web/src/components/landing/portability.tsx +190 -0
  422. package/apps/web/src/components/landing/pricing.tsx +263 -0
  423. package/apps/web/src/components/landing/scaling.tsx +209 -0
  424. package/apps/web/src/components/landing/why-openship.tsx +291 -0
  425. package/apps/web/src/hooks/use-gsap-scroll.ts +158 -0
  426. package/apps/web/src/hooks/use-platform.ts +86 -0
  427. package/apps/web/src/lib/source.ts +12 -0
  428. package/apps/web/src/styles/fonts.css +84 -0
  429. package/apps/web/src/styles/landing.css +951 -0
  430. package/apps/web/src/styles/theme.css +227 -0
  431. package/apps/web/tsconfig.json +8 -3
  432. package/bun.lock +3260 -0
  433. package/docker-compose.yml +3 -0
  434. package/docs/screenshots/.gitkeep +0 -0
  435. package/docs/screenshots/screen.png +0 -0
  436. package/openship.code-workspace +7 -0
  437. package/package.json +23 -9
  438. package/packages/adapters/docs/ARCHITECTURE.md +160 -0
  439. package/packages/adapters/docs/BUILD-PIPELINE.md +171 -0
  440. package/packages/adapters/docs/CLOUD.md +239 -0
  441. package/packages/adapters/docs/EXECUTOR.md +174 -0
  442. package/packages/adapters/docs/LOG-STREAMING.md +175 -0
  443. package/packages/adapters/docs/SYSTEM.md +191 -0
  444. package/packages/adapters/package.json +14 -9
  445. package/packages/adapters/src/index.ts +144 -4
  446. package/packages/adapters/src/infra/cloud.ts +42 -0
  447. package/packages/adapters/src/infra/index.ts +9 -0
  448. package/packages/adapters/src/infra/noop.ts +27 -0
  449. package/packages/adapters/src/infra/traefik.ts +297 -0
  450. package/packages/adapters/src/infra/types.ts +36 -0
  451. package/packages/adapters/src/platform.ts +255 -0
  452. package/packages/adapters/src/runtime/bare.ts +567 -0
  453. package/packages/adapters/src/runtime/build-pipeline.ts +237 -0
  454. package/packages/adapters/src/runtime/cloud.ts +791 -0
  455. package/packages/adapters/src/runtime/deploy-pipeline.ts +146 -0
  456. package/packages/adapters/src/runtime/docker.ts +443 -0
  457. package/packages/adapters/src/runtime/index.ts +69 -0
  458. package/packages/adapters/src/runtime/local-build.ts +112 -0
  459. package/packages/adapters/src/runtime/transfer.ts +82 -0
  460. package/packages/adapters/src/runtime/types.ts +138 -0
  461. package/packages/adapters/src/system/catalog.ts +188 -0
  462. package/packages/adapters/src/system/checks.ts +228 -0
  463. package/packages/adapters/src/system/components.ts +45 -0
  464. package/packages/adapters/src/system/debug.ts +18 -0
  465. package/packages/adapters/src/system/environment.ts +168 -0
  466. package/packages/adapters/src/system/errors.ts +34 -0
  467. package/packages/adapters/src/system/executor.ts +600 -0
  468. package/packages/adapters/src/system/index.ts +58 -0
  469. package/packages/adapters/src/system/installer.ts +310 -0
  470. package/packages/adapters/src/system/setup.ts +442 -0
  471. package/packages/adapters/src/system/state.ts +96 -0
  472. package/packages/adapters/src/system/types.ts +116 -0
  473. package/packages/adapters/src/toolchain/catalog.ts +439 -0
  474. package/packages/adapters/src/toolchain/checks.ts +181 -0
  475. package/packages/adapters/src/toolchain/index.ts +38 -0
  476. package/packages/adapters/src/toolchain/installer.ts +189 -0
  477. package/packages/adapters/src/toolchain/types.ts +84 -0
  478. package/packages/adapters/src/types.ts +277 -0
  479. package/packages/adapters/tsconfig.json +3 -1
  480. package/packages/core/package.json +9 -9
  481. package/packages/core/src/constants.ts +44 -1
  482. package/packages/core/src/index.ts +2 -0
  483. package/packages/core/src/stacks.ts +778 -0
  484. package/packages/core/src/system.ts +111 -0
  485. package/packages/core/src/types.ts +22 -1
  486. package/packages/core/src/utils.ts +8 -0
  487. package/packages/core/tsconfig.json +3 -1
  488. package/packages/db/drizzle/0000_init.sql +231 -0
  489. package/packages/db/drizzle/0001_add_server_name.sql +1 -0
  490. package/packages/db/drizzle/meta/_journal.json +20 -0
  491. package/packages/db/drizzle.config.ts +10 -0
  492. package/packages/db/package.json +26 -14
  493. package/packages/db/src/client.ts +119 -0
  494. package/packages/db/src/index.ts +57 -8
  495. package/packages/db/src/repos/account.repo.ts +92 -0
  496. package/packages/db/src/repos/deployment.repo.ts +168 -0
  497. package/packages/db/src/repos/domain.repo.ts +102 -0
  498. package/packages/db/src/repos/git-installation.repo.ts +95 -0
  499. package/packages/db/src/repos/index.ts +43 -0
  500. package/packages/db/src/repos/instance-settings.repo.ts +43 -0
  501. package/packages/db/src/repos/project.repo.ts +159 -0
  502. package/packages/db/src/repos/session.repo.ts +82 -0
  503. package/packages/db/src/repos/settings.repo.ts +51 -0
  504. package/packages/db/src/repos/user.repo.ts +94 -0
  505. package/packages/db/src/schema/auth.ts +56 -0
  506. package/packages/db/src/schema/deployment.ts +87 -0
  507. package/packages/db/src/schema/domain.ts +46 -0
  508. package/packages/db/src/schema/github.ts +24 -0
  509. package/packages/db/src/schema/index.ts +6 -0
  510. package/packages/db/src/schema/project.ts +117 -0
  511. package/packages/db/src/schema/settings.ts +103 -0
  512. package/packages/db/tsconfig.json +3 -1
  513. package/packages/ui/package.json +17 -17
  514. package/packages/ui/src/components/status-dot.tsx +1 -1
  515. package/packages/ui/tsconfig.json +7 -2
  516. package/pnpm-workspace.yaml +0 -1
  517. package/{tooling/tsconfig/base.json → tsconfig.base.json} +0 -1
  518. package/turbo.json +4 -0
  519. package/apps/api/src/modules/auth/auth.controller.ts +0 -26
  520. package/apps/api/src/modules/auth/auth.schema.ts +0 -12
  521. package/apps/api/src/modules/auth/auth.service.ts +0 -23
  522. package/apps/dashboard/src/app/page.tsx +0 -6
  523. package/apps/web/src/app/(marketing)/docs/page.tsx +0 -10
  524. package/packages/adapters/src/base-adapter.ts +0 -49
  525. package/packages/adapters/src/docker-adapter.ts +0 -43
  526. package/packages/adapters/src/oblien-adapter.ts +0 -46
  527. package/packages/adapters/src/registry.ts +0 -22
  528. package/packages/db/prisma/schema.prisma +0 -160
  529. package/tooling/tsconfig/nextjs.json +0 -10
  530. package/tooling/tsconfig/node.json +0 -10
  531. package/tooling/tsconfig/package.json +0 -7
@@ -0,0 +1,185 @@
1
+ /* ── comparison rows ── */
2
+ const ROWS: { feature: string; openship: string; vercel: string; warn?: boolean }[] = [
3
+ { feature: "Unlimited deployments", openship: "Included", vercel: "$20 / seat / mo", warn: true },
4
+ { feature: "CPU execution time", openship: "Your full server", vercel: "Metered — bills spike", warn: true },
5
+ { feature: "Bandwidth", openship: "Unlimited", vercel: "100 GB then overage", warn: true },
6
+ { feature: "Serverless invocations", openship: "No limits", vercel: "Billed per million", warn: true },
7
+ { feature: "Wildcard SSL", openship: "Auto", vercel: "Enterprise only" },
8
+ { feature: "Built-in mail server", openship: "Free", vercel: "—" },
9
+ { feature: "Microservices / Docker", openship: "Native", vercel: "—" },
10
+ { feature: "Custom backend & DB", openship: "Full control", vercel: "External only" },
11
+ { feature: "Full-control app", openship: "Built-in", vercel: "Limited" },
12
+ { feature: "MCP agent support", openship: "Built-in", vercel: "—" },
13
+ { feature: "Self-host anywhere", openship: "Any VPS", vercel: "—" },
14
+ { feature: "Vendor lock-in", openship: "None — MIT", vercel: "High" },
15
+ ];
16
+
17
+ /* ── VPS providers ── */
18
+ const VPS = [
19
+ { name: "Hetzner", from: "€3.79", note: "CX22 · 2 vCPU · 4 GB", accent: "#E32E2E" },
20
+ { name: "Hostinger", from: "$5.99", note: "KVM 1 · 4 GB · 50 GB NVMe", accent: "#6746C8" },
21
+ { name: "DigitalOcean", from: "$6.00", note: "Basic · 1 vCPU · 1 GB", accent: "#0080FF" },
22
+ { name: "OVH", from: "€3.50", note: "Starter · 2 GB · ∞ traffic", accent: "#123F6D" },
23
+ ];
24
+
25
+ /* ── vercel bill callout figures ── */
26
+ const BILLS = [
27
+ { label: "Small SaaS", vercel: "$150+/mo", self: "~$8/mo" },
28
+ { label: "Growing startup", vercel: "$800+/mo", self: "~$24/mo" },
29
+ { label: "At scale", vercel: "$5,000+/mo", self: "~$80/mo" },
30
+ ];
31
+
32
+ export function NoLockin() {
33
+ return (
34
+ <section className="relative py-28 sm:py-36">
35
+ <div className="section-divider mx-auto mb-28 max-w-4xl sm:mb-36" />
36
+
37
+ <div className="mx-auto max-w-7xl px-6">
38
+ {/* ── Header ── */}
39
+ <div className="mx-auto max-w-3xl text-center">
40
+ <div className="mb-7 inline-flex items-center gap-2 rounded-full border border-[var(--th-on-06)] bg-white/60 px-4 py-1.5 shadow-[0_1px_3px_rgba(0,0,0,.04)] backdrop-blur-sm">
41
+ <span className="text-[12px] font-medium tracking-[0.02em] th-text-secondary">
42
+ Why switch
43
+ </span>
44
+ </div>
45
+ <h2>
46
+ <span className="block text-[clamp(2.75rem,6vw,4.75rem)] font-semibold leading-[1.06] tracking-[-0.035em] th-text-heading">
47
+ Stop getting locked in.
48
+ </span>
49
+ <span
50
+ className="block text-[clamp(2.75rem,6vw,4.75rem)] font-semibold leading-[1.06] tracking-[-0.035em]"
51
+ style={{ color: "var(--th-on-40)" }}
52
+ >
53
+ Own your stack.
54
+ </span>
55
+ </h2>
56
+ <p className="mx-auto mt-6 max-w-xl text-[17px] leading-[1.65] th-text-body">
57
+ Vercel&apos;s metered CPU time, per-seat pricing, and bandwidth caps
58
+ mean bills grow fast — especially when traffic spikes. With Openship,
59
+ the app runs on <strong className="th-text-heading">your machine</strong> and deploys to
60
+ <strong className="th-text-heading"> your server</strong> — you keep 100% of the performance you pay for.
61
+ </p>
62
+ </div>
63
+
64
+ {/* ── Bill comparison callout ── */}
65
+ <div className="mx-auto mt-14 grid max-w-5xl gap-4 sm:grid-cols-3">
66
+ {BILLS.map((b) => (
67
+ <div
68
+ key={b.label}
69
+ className="relative overflow-hidden rounded-2xl border border-[var(--th-card-bd)] bg-[var(--th-card-bg)] px-6 py-5 shadow-[var(--th-card-shadow)]"
70
+ >
71
+ <p className="text-[12px] font-semibold uppercase tracking-[0.1em] th-text-muted">
72
+ {b.label}
73
+ </p>
74
+
75
+ {/* Vercel row */}
76
+ <div className="mt-4">
77
+ <p className="text-[11px] font-medium uppercase tracking-[0.06em] th-text-muted">Vercel</p>
78
+ <p
79
+ className="mt-0.5 text-[clamp(1.25rem,2.5vw,1.625rem)] font-bold leading-none tracking-tight"
80
+ style={{ color: "var(--th-clr-terra)" }}
81
+ >
82
+ {b.vercel}
83
+ </p>
84
+ </div>
85
+
86
+ {/* Divider + arrow */}
87
+ <div className="my-3 flex items-center gap-2">
88
+ <div className="h-px flex-1" style={{ background: "var(--th-sf-06, rgba(0,0,0,.06))" }} />
89
+ <svg className="h-4 w-4 shrink-0 th-text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
90
+ <path strokeLinecap="round" strokeLinejoin="round" d="M19.5 13.5L12 21m0 0l-7.5-7.5M12 21V3" />
91
+ </svg>
92
+ <div className="h-px flex-1" style={{ background: "var(--th-sf-06, rgba(0,0,0,.06))" }} />
93
+ </div>
94
+
95
+ {/* Self-host row */}
96
+ <div>
97
+ <p className="text-[11px] font-medium uppercase tracking-[0.06em] th-text-muted">Self-host</p>
98
+ <p
99
+ className="mt-0.5 text-[clamp(1.25rem,2.5vw,1.625rem)] font-bold leading-none tracking-tight"
100
+ style={{ color: "var(--th-clr-sea)" }}
101
+ >
102
+ {b.self}
103
+ </p>
104
+ </div>
105
+ </div>
106
+ ))}
107
+ </div>
108
+
109
+ {/* ── Comparison table ── */}
110
+ <div className="mx-auto mt-14 max-w-5xl overflow-hidden rounded-2xl border border-[var(--th-card-bd)] bg-[var(--th-card-bg)] shadow-[var(--th-card-shadow)]">
111
+ {/* Header row */}
112
+ <div className="grid grid-cols-[1fr_140px_160px] sm:grid-cols-[1fr_200px_220px] border-b-2 border-[var(--th-card-bd)] bg-[var(--th-sf-02)] px-6 py-4">
113
+ <span className="text-[13px] font-bold uppercase tracking-[0.08em] th-text-muted">
114
+ Feature
115
+ </span>
116
+ <span className="text-center text-[14px] font-bold th-text-heading">
117
+ 🚀 Openship
118
+ </span>
119
+ <span className="text-center text-[14px] font-bold th-text-muted">
120
+ Vercel
121
+ </span>
122
+ </div>
123
+
124
+ {/* Data rows */}
125
+ {ROWS.map((r, i) => (
126
+ <div
127
+ key={r.feature}
128
+ className="grid grid-cols-[1fr_140px_160px] sm:grid-cols-[1fr_200px_220px] items-center border-t border-[var(--th-card-bd)] px-6 py-4"
129
+ style={{ background: i % 2 === 0 ? "transparent" : "var(--th-sf-01)" }}
130
+ >
131
+ <span className="text-[15px] font-medium th-text-heading">
132
+ {r.feature}
133
+ </span>
134
+ <span className="text-center">
135
+ <span
136
+ className="inline-block rounded-full px-3.5 py-1 text-[13px] font-semibold"
137
+ style={{ background: "var(--th-clr-sea-wash)", color: "var(--th-clr-sea)" }}
138
+ >
139
+ {r.openship}
140
+ </span>
141
+ </span>
142
+ <span className="text-center">
143
+ <span
144
+ className="inline-block rounded-full px-3.5 py-1 text-[13px] font-medium"
145
+ style={{
146
+ background: r.warn ? "var(--th-clr-terra-wash)" : "var(--th-sf-04)",
147
+ color: r.warn ? "var(--th-clr-terra)" : "var(--th-text-secondary)",
148
+ }}
149
+ >
150
+ {r.vercel}
151
+ </span>
152
+ </span>
153
+ </div>
154
+ ))}
155
+ </div>
156
+
157
+ {/* ── VPS provider cards ── */}
158
+ <div className="mt-12">
159
+ <p className="mb-5 text-center text-[13px] font-semibold uppercase tracking-[0.08em] th-text-muted">
160
+ Self-host on any VPS — popular picks
161
+ </p>
162
+ <div className="mx-auto grid max-w-4xl grid-cols-2 gap-4 sm:grid-cols-4">
163
+ {VPS.map((v) => (
164
+ <div
165
+ key={v.name}
166
+ className="relative overflow-hidden rounded-2xl border border-[var(--th-card-bd)] bg-[var(--th-card-bg)] p-5 shadow-[var(--th-card-shadow)] transition-colors hover:border-[var(--th-card-bd-hover)]"
167
+ >
168
+ <div
169
+ className="absolute inset-x-0 top-0 h-[3px]"
170
+ style={{ background: v.accent }}
171
+ />
172
+ <p className="text-[15px] font-bold th-text-heading">{v.name}</p>
173
+ <p className="mt-1 text-[24px] font-bold tracking-tight th-text-heading">
174
+ {v.from}
175
+ <span className="ml-1 text-[13px] font-normal th-text-muted">/mo</span>
176
+ </p>
177
+ <p className="mt-1.5 text-[12px] leading-snug th-text-muted">{v.note}</p>
178
+ </div>
179
+ ))}
180
+ </div>
181
+ </div>
182
+ </div>
183
+ </section>
184
+ );
185
+ }
@@ -0,0 +1,151 @@
1
+ const TRAITS = [
2
+ {
3
+ icon: (
4
+ <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.6} className="h-5 w-5">
5
+ <path strokeLinecap="round" strokeLinejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.955 11.955 0 003 12c0 6.628 5.373 12 12 12s12-5.372 12-12c0-1.99-.487-3.87-1.346-5.528A11.95 11.95 0 0112 2.964z" />
6
+ </svg>
7
+ ),
8
+ label: "MIT License",
9
+ desc: "No vendor lock-in. Fork it, own it.",
10
+ },
11
+ {
12
+ icon: (
13
+ <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.6} className="h-5 w-5">
14
+ <path strokeLinecap="round" strokeLinejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" />
15
+ </svg>
16
+ ),
17
+ label: "Free Mail Server",
18
+ desc: "Transactional email — no Sendgrid bill.",
19
+ },
20
+ {
21
+ icon: (
22
+ <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.6} className="h-5 w-5">
23
+ <path strokeLinecap="round" strokeLinejoin="round" d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418" />
24
+ </svg>
25
+ ),
26
+ label: "Unlimited Domains",
27
+ desc: "Wildcard SSL included, always.",
28
+ },
29
+ {
30
+ icon: (
31
+ <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.6} className="h-5 w-5">
32
+ <path strokeLinecap="round" strokeLinejoin="round" d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" />
33
+ </svg>
34
+ ),
35
+ label: "No Usage Limits",
36
+ desc: "Deploy as much as your server can handle.",
37
+ },
38
+ {
39
+ icon: (
40
+ <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.6} className="h-5 w-5">
41
+ <path strokeLinecap="round" strokeLinejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" />
42
+ </svg>
43
+ ),
44
+ label: "Community Driven",
45
+ desc: "Built in public, with contributors worldwide.",
46
+ },
47
+ {
48
+ icon: (
49
+ <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.6} className="h-5 w-5">
50
+ <path strokeLinecap="round" strokeLinejoin="round" d="M5.25 14.25h13.5m-13.5 0a3 3 0 01-3-3m3 3a3 3 0 100 6h13.5a3 3 0 100-6m-16.5-3a3 3 0 013-3h13.5a3 3 0 013 3m-19.5 0a4.5 4.5 0 01.9-2.7L5.737 5.1a3.375 3.375 0 012.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 01.9 2.7m0 0a3 3 0 01-3 3m0 3h.008v.008h-.008v-3zm0 0H12m-16.5-3h.008v.008H3.75V12z" />
51
+ </svg>
52
+ ),
53
+ label: "Self-Host Anywhere",
54
+ desc: "Your VPS, bare-metal, or private cloud.",
55
+ },
56
+ ];
57
+
58
+ export function OpenSourceCta() {
59
+ return (
60
+ <section className="relative py-28 sm:py-36">
61
+ {/* Divider */}
62
+ <div className="section-divider mx-auto mb-28 max-w-4xl sm:mb-36" />
63
+
64
+ <div className="mx-auto max-w-7xl px-6">
65
+ <div className="grid gap-16 lg:grid-cols-[1fr_auto] lg:gap-24 lg:items-start">
66
+
67
+ {/* ── Left: headline + CTAs ── */}
68
+ <div>
69
+ {/* Badge */}
70
+ <div className="mb-7 inline-flex items-center gap-2 rounded-full border border-[var(--th-on-06)] bg-white/60 px-4 py-1.5 shadow-[0_1px_3px_rgba(0,0,0,.04)] backdrop-blur-sm">
71
+ <svg className="h-3.5 w-3.5 th-text-muted" fill="currentColor" viewBox="0 0 24 24">
72
+ <path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
73
+ </svg>
74
+ <span className="text-[12px] font-medium tracking-[0.02em] th-text-secondary">
75
+ Open Source &middot; MIT License
76
+ </span>
77
+ </div>
78
+
79
+ {/* Headline */}
80
+ <h2>
81
+ <span className="block text-[clamp(2.75rem,6vw,4.75rem)] font-semibold leading-[1.06] tracking-[-0.035em] th-text-heading">
82
+ Free. Open Source.
83
+ </span>
84
+ <span
85
+ className="block text-[clamp(2.75rem,6vw,4.75rem)] font-semibold leading-[1.06] tracking-[-0.035em]"
86
+ style={{ color: "var(--th-on-40)" }}
87
+ >
88
+ Forever.
89
+ </span>
90
+ </h2>
91
+
92
+ <p className="mt-6 max-w-[480px] text-[16px] leading-[1.65] th-text-body">
93
+ MIT licensed and built in public. Deploy to
94
+ Openship Cloud, self-host on your own server, or contribute to the codebase.{" "}
95
+ No hidden costs, no usage limits, no lock-in.
96
+ </p>
97
+
98
+ {/* CTAs */}
99
+ <div className="mt-9 flex flex-col gap-3.5 sm:flex-row sm:items-center">
100
+ <a
101
+ href="/login"
102
+ className="th-btn group rounded-full px-7 py-3 text-[15px] font-medium"
103
+ >
104
+ Start Free
105
+ <svg
106
+ className="ml-1.5 h-4 w-4 transition-transform group-hover:translate-x-0.5"
107
+ fill="none"
108
+ viewBox="0 0 24 24"
109
+ stroke="currentColor"
110
+ strokeWidth={2.2}
111
+ >
112
+ <path strokeLinecap="round" strokeLinejoin="round" d="M13 7l5 5m0 0l-5 5m5-5H6" />
113
+ </svg>
114
+ </a>
115
+ <a
116
+ href="https://github.com/openshiporg/openship"
117
+ target="_blank"
118
+ rel="noopener noreferrer"
119
+ className="th-btn-ghost rounded-full px-7 py-3 text-[15px]"
120
+ >
121
+ <svg className="h-[18px] w-[18px]" fill="currentColor" viewBox="0 0 24 24">
122
+ <path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
123
+ </svg>
124
+ View on GitHub
125
+ </a>
126
+ </div>
127
+ </div>
128
+
129
+ {/* ── Right: trait grid ── */}
130
+ <div className="grid grid-cols-1 gap-px sm:grid-cols-2 lg:grid-cols-1 lg:w-[360px] rounded-2xl overflow-hidden border border-[var(--th-card-bd)] bg-[var(--th-card-bd)]">
131
+ {TRAITS.map((t) => (
132
+ <div
133
+ key={t.label}
134
+ className="flex items-start gap-4 bg-[var(--th-card-bg)] px-5 py-4"
135
+ >
136
+ <span className="mt-0.5 flex-none rounded-lg border border-[var(--th-card-bd)] p-2 th-text-secondary">
137
+ {t.icon}
138
+ </span>
139
+ <div>
140
+ <p className="text-[14px] font-semibold th-text-heading">{t.label}</p>
141
+ <p className="mt-0.5 text-[13px] leading-snug th-text-body">{t.desc}</p>
142
+ </div>
143
+ </div>
144
+ ))}
145
+ </div>
146
+
147
+ </div>
148
+ </div>
149
+ </section>
150
+ );
151
+ }
@@ -0,0 +1,190 @@
1
+ const DIRECTIONS = [
2
+ {
3
+ from: "Self-hosted VPS",
4
+ to: "Openship Cloud",
5
+ icon: (
6
+ <svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
7
+ <path strokeLinecap="round" strokeLinejoin="round" d="M2.25 15a4.5 4.5 0 004.5 4.5H18a3.75 3.75 0 001.332-7.257 3 3 0 00-3.758-3.848 5.25 5.25 0 00-10.233 2.33A4.502 4.502 0 002.25 15z" />
8
+ </svg>
9
+ ),
10
+ desc: "Want managed scaling? Push your self-hosted instance to Openship Cloud — projects, domains, certs, and databases transfer instantly. Control everything from the app.",
11
+ color: "var(--th-clr-plum)",
12
+ bgWash: "var(--th-clr-plum-bg)",
13
+ blob: "var(--th-clr-plum-blob)",
14
+ },
15
+ {
16
+ from: "Openship Cloud",
17
+ to: "Self-hosted VPS",
18
+ icon: (
19
+ <svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
20
+ <path strokeLinecap="round" strokeLinejoin="round" d="M5.25 14.25h13.5m-13.5 0a3 3 0 01-3-3m3 3a3 3 0 100 6h13.5a3 3 0 100-6m-16.5-3a3 3 0 013-3h13.5a3 3 0 013 3m-19.5 0a4.5 4.5 0 01.9-2.7L5.737 5.1a3.375 3.375 0 012.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 01.9 2.7m0 0a3 3 0 01-3 3m0 3h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008zm-3 6h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008z" />
21
+ </svg>
22
+ ),
23
+ desc: "Want full control? Export from Openship Cloud to your own server in one tap. You own the data — we never hold it hostage.",
24
+ color: "var(--th-clr-sea)",
25
+ bgWash: "var(--th-clr-sea-bg)",
26
+ blob: "var(--th-clr-sea-blob)",
27
+ },
28
+ {
29
+ from: "Server A",
30
+ to: "Server B",
31
+ icon: (
32
+ <svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
33
+ <path strokeLinecap="round" strokeLinejoin="round" d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5" />
34
+ </svg>
35
+ ),
36
+ desc: "Moving between VPS providers? Hetzner to DigitalOcean, OVH to Hostinger — migrate everything without touching a config file.",
37
+ color: "var(--th-clr-terra)",
38
+ bgWash: "var(--th-clr-terra-bg)",
39
+ blob: "var(--th-clr-terra-blob)",
40
+ },
41
+ ];
42
+
43
+ const TRUST_POINTS = [
44
+ "No proprietary formats — standard Docker + volumes",
45
+ "CLI export produces a portable .tar.gz archive",
46
+ "Encrypted transfer over SSH by default",
47
+ "Domains & SSL auto-reconnect on the new host",
48
+ "Works between any Linux server, any provider",
49
+ ];
50
+
51
+ const CHECK = (
52
+ <svg className="mt-0.5 h-4 w-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.2}>
53
+ <path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
54
+ </svg>
55
+ );
56
+
57
+ export function Portability() {
58
+ return (
59
+ <section className="relative py-28 sm:py-36">
60
+ <div className="section-divider mx-auto mb-28 max-w-4xl sm:mb-36" />
61
+
62
+ <div className="mx-auto max-w-7xl px-6">
63
+ {/* ── Header ── */}
64
+ <div className="mx-auto max-w-3xl text-center">
65
+ <div className="mb-7 inline-flex items-center gap-2 rounded-full border border-[var(--th-on-06)] bg-white/60 px-4 py-1.5 shadow-[0_1px_3px_rgba(0,0,0,.04)] backdrop-blur-sm">
66
+ <span className="text-[12px] font-medium tracking-[0.02em] th-text-secondary">
67
+ Portability
68
+ </span>
69
+ </div>
70
+ <h2>
71
+ <span className="block text-[clamp(2.75rem,6vw,4.75rem)] font-semibold leading-[1.06] tracking-[-0.035em] th-text-heading">
72
+ Cloud ↔ self-host.
73
+ </span>
74
+ <span
75
+ className="block text-[clamp(2.75rem,6vw,4.75rem)] font-semibold leading-[1.06] tracking-[-0.035em]"
76
+ style={{ color: "var(--th-on-40)" }}
77
+ >
78
+ Move anytime. Zero setup.
79
+ </span>
80
+ </h2>
81
+ <p className="mx-auto mt-6 max-w-xl text-[17px] leading-[1.65] th-text-body">
82
+ Your infrastructure should never be a cage. Transfer between Openship
83
+ Cloud and any self-hosted VPS — or between two servers — in a single
84
+ tap from the app. No re-deploy, no data loss, no vendor games.
85
+ </p>
86
+ </div>
87
+
88
+ {/* ── Direction cards ── */}
89
+ <div className="mx-auto mt-16 grid max-w-6xl gap-6 md:grid-cols-3">
90
+ {DIRECTIONS.map((d) => (
91
+ <div
92
+ key={d.from + d.to}
93
+ className="relative overflow-hidden rounded-2xl border border-[var(--th-card-bd)] bg-[var(--th-card-bg)] p-7 shadow-[var(--th-card-shadow)] transition-colors hover:border-[var(--th-card-bd-hover)]"
94
+ >
95
+ {/* Accent top bar */}
96
+ <div
97
+ className="absolute inset-x-0 top-0 h-[3px]"
98
+ style={{ background: d.color }}
99
+ />
100
+
101
+ {/* Ambient blob */}
102
+ <div
103
+ className="pointer-events-none absolute -right-10 -top-10 h-[160px] w-[160px] rounded-full blur-[60px]"
104
+ style={{ background: d.blob }}
105
+ />
106
+
107
+ <div className="relative">
108
+ <div
109
+ className="mb-5 flex h-12 w-12 items-center justify-center rounded-xl"
110
+ style={{ background: d.bgWash, color: d.color }}
111
+ >
112
+ {d.icon}
113
+ </div>
114
+
115
+ {/* From → To — bigger & bolder */}
116
+ <div className="mb-3 flex flex-wrap items-center gap-2">
117
+ <span className="text-[17px] font-bold th-text-heading">{d.from}</span>
118
+ <svg className="h-4 w-4 th-text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
119
+ <path strokeLinecap="round" strokeLinejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3" />
120
+ </svg>
121
+ <span className="text-[17px] font-bold th-text-heading">{d.to}</span>
122
+ </div>
123
+
124
+ <p className="text-[14px] leading-[1.65] th-text-body">{d.desc}</p>
125
+ </div>
126
+ </div>
127
+ ))}
128
+ </div>
129
+
130
+ {/* ── CLI demo + trust points ── */}
131
+ <div className="mx-auto mt-14 grid max-w-5xl items-start gap-10 lg:grid-cols-[1fr_1fr]">
132
+ {/* Terminal card */}
133
+ <div className="overflow-hidden rounded-2xl border border-[var(--th-card-bd)] bg-[var(--th-card-bg)] font-mono text-[13px] shadow-[var(--th-card-shadow)]">
134
+ <div className="border-b border-[var(--th-card-bd)] px-5 py-2.5 text-[11px] font-semibold uppercase tracking-[0.1em] th-text-muted">
135
+ Terminal
136
+ </div>
137
+ <div className="space-y-2 px-5 py-4">
138
+ <div>
139
+ <span className="th-text-muted">$</span>{" "}
140
+ <span className="th-text-heading">openship export</span>
141
+ </div>
142
+ <div className="th-text-muted">
143
+ ✓ 3 projects, 2 databases, 4 domains
144
+ </div>
145
+ <div className="th-text-muted">
146
+ ✓ Packed → openship-backup-2026-03-07.tar.gz (142 MB)
147
+ </div>
148
+ <div className="mt-3">
149
+ <span className="th-text-muted">$</span>{" "}
150
+ <span className="th-text-heading">openship import --host new-server.example.com</span>
151
+ </div>
152
+ <div className="th-text-muted">
153
+ ✓ Transferred via SSH (encrypted)
154
+ </div>
155
+ <div className="th-text-muted">
156
+ ✓ Projects restored, domains reconnected
157
+ </div>
158
+ <div style={{ color: "var(--th-clr-sea)" }}>
159
+ ✓ Migration complete — 0 downtime
160
+ </div>
161
+ </div>
162
+ </div>
163
+
164
+ {/* Trust points */}
165
+ <div>
166
+ <h3 className="mb-5 text-[20px] font-bold th-text-heading">
167
+ True portability, not marketing talk.
168
+ </h3>
169
+ <ul className="space-y-3">
170
+ {TRUST_POINTS.map((t) => (
171
+ <li
172
+ key={t}
173
+ className="flex items-start gap-2.5 text-[15px] th-text-heading"
174
+ >
175
+ <span style={{ color: "var(--th-clr-sea)" }}>{CHECK}</span>
176
+ {t}
177
+ </li>
178
+ ))}
179
+ </ul>
180
+ <p className="mt-6 text-[14px] leading-[1.65] th-text-body">
181
+ Your stack is standard Docker. There is no proprietary runtime, no
182
+ secret sauce tying you down. Leave whenever you want — or never
183
+ leave at all. It&apos;s your call.
184
+ </p>
185
+ </div>
186
+ </div>
187
+ </div>
188
+ </section>
189
+ );
190
+ }