vibecarbon 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/carbon/biome.json +3 -2
  2. package/carbon/package.json +20 -20
  3. package/carbon/scripts/dev-init.js +6 -2
  4. package/carbon/src/client/assets/vibecarbon-icon.svg +6 -6
  5. package/carbon/src/client/assets/vibecarbon-wordmark-text-dark.svg +12 -0
  6. package/carbon/src/client/assets/vibecarbon-wordmark-text-light.svg +12 -0
  7. package/carbon/src/client/components/AIIntegrationSection.tsx +1 -3
  8. package/carbon/src/client/components/AppSidebar.tsx +35 -28
  9. package/carbon/src/client/components/CTAFooter.tsx +1 -1
  10. package/carbon/src/client/components/ComparisonSection.tsx +1 -3
  11. package/carbon/src/client/components/ContentPanel.tsx +1 -1
  12. package/carbon/src/client/components/DeploymentScenariosSection.tsx +1 -3
  13. package/carbon/src/client/components/FAQSection.tsx +1 -1
  14. package/carbon/src/client/components/Hero.tsx +13 -1
  15. package/carbon/src/client/components/Logo.tsx +66 -49
  16. package/carbon/src/client/components/Nav.tsx +1 -1
  17. package/carbon/src/client/components/NewsletterSignup.tsx +1 -1
  18. package/carbon/src/client/components/NotificationDrawer.tsx +3 -3
  19. package/carbon/src/client/components/PageHeader.tsx +1 -1
  20. package/carbon/src/client/components/PricingSection.tsx +2 -6
  21. package/carbon/src/client/components/TechStackSection.tsx +1 -3
  22. package/carbon/src/client/components/effects/FilmGrainOverlay.tsx +5 -0
  23. package/carbon/src/client/components/ui/button.tsx +1 -1
  24. package/carbon/src/client/components/ui/card.tsx +1 -1
  25. package/carbon/src/client/components/ui/dialog.tsx +1 -1
  26. package/carbon/src/client/components/ui/dropdown-menu.tsx +2 -2
  27. package/carbon/src/client/components/ui/popover.tsx +1 -1
  28. package/carbon/src/client/components/ui/select.tsx +1 -1
  29. package/carbon/src/client/components/ui/sidebar.tsx +1 -1
  30. package/carbon/src/client/index.css +186 -104
  31. package/carbon/src/client/lib/theme.ts +14 -14
  32. package/carbon/src/client/locales/en.json +4 -4
  33. package/carbon/src/client/pages/Blog.tsx +1 -1
  34. package/carbon/src/client/pages/Changelog.tsx +1 -1
  35. package/carbon/src/client/pages/Dashboard.tsx +33 -40
  36. package/carbon/src/client/pages/Docs.tsx +1 -1
  37. package/carbon/src/client/pages/Home.tsx +1 -1
  38. package/carbon/src/client/pages/Legal.tsx +1 -1
  39. package/carbon/src/server/lib/env.ts +1 -1
  40. package/package.json +13 -13
  41. package/src/lib/orphan.js +221 -0
  42. package/src/up.js +22 -7
  43. package/carbon/src/client/assets/vibecarbon-logo-dark.svg +0 -29
  44. package/carbon/src/client/assets/vibecarbon-logo-light.svg +0 -29
@@ -11,6 +11,7 @@ import {
11
11
  UserPlus,
12
12
  Users,
13
13
  } from 'lucide-react';
14
+ import { useId } from 'react';
14
15
  import { useTranslation } from 'react-i18next';
15
16
  import { Area, AreaChart } from 'recharts';
16
17
  import { useAuth } from '../components/auth/AuthProvider';
@@ -21,7 +22,7 @@ import { ChartContainer } from '../components/ui/chart';
21
22
  import { Skeleton } from '../components/ui/skeleton';
22
23
  import { adminServices, getServiceUrl } from '../lib/admin-services';
23
24
  import { getAuthHeaders } from '../lib/api';
24
- import { getUserInitials } from '../lib/utils';
25
+ import { cn, getUserInitials } from '../lib/utils';
25
26
 
26
27
  const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || 'http://localhost:8000';
27
28
 
@@ -49,24 +50,25 @@ function getGreetingKey() {
49
50
  }
50
51
 
51
52
  function MiniSparkline({ data }: { data: Array<{ week: string; count: number }> }) {
53
+ const gradientId = useId();
52
54
  return (
53
55
  <ChartContainer
54
- config={{ count: { label: 'Signups', color: 'rgba(255,255,255,0.5)' } }}
55
- className="h-16 w-full aspect-auto"
56
+ config={{ count: { label: 'Signups', color: 'var(--chart-1)' } }}
57
+ className="h-16 w-full aspect-auto [&_.recharts-area-curve]:[filter:drop-shadow(0_0_5px_var(--glow-primary))]"
56
58
  >
57
59
  <AreaChart data={data} margin={{ top: 4, right: 0, bottom: 0, left: 0 }}>
58
60
  <defs>
59
- <linearGradient id="sparkFill" x1="0" y1="0" x2="0" y2="1">
60
- <stop offset="0%" stopColor="rgba(255,255,255,0.3)" />
61
- <stop offset="100%" stopColor="rgba(255,255,255,0.05)" />
61
+ <linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="1">
62
+ <stop offset="0%" stopColor="var(--color-count)" stopOpacity={0.25} />
63
+ <stop offset="100%" stopColor="var(--color-count)" stopOpacity={0.03} />
62
64
  </linearGradient>
63
65
  </defs>
64
66
  <Area
65
67
  dataKey="count"
66
68
  type="monotone"
67
- stroke="rgba(255,255,255,0.6)"
69
+ stroke="var(--color-count)"
68
70
  strokeWidth={2}
69
- fill="url(#sparkFill)"
71
+ fill={`url(#${gradientId})`}
70
72
  />
71
73
  </AreaChart>
72
74
  </ChartContainer>
@@ -79,8 +81,6 @@ interface KpiCardProps {
79
81
  change: string;
80
82
  positive: boolean;
81
83
  icon: React.ComponentType<{ className?: string }>;
82
- accentColor: string;
83
- accentVar: string;
84
84
  sparklineData?: Array<{ week: string; count: number }>;
85
85
  loading: boolean;
86
86
  }
@@ -91,26 +91,18 @@ function KpiCard({
91
91
  change,
92
92
  positive,
93
93
  icon: Icon,
94
- accentColor: _accentColor,
95
- accentVar,
96
94
  sparklineData,
97
95
  loading,
98
96
  }: KpiCardProps) {
99
97
  return (
100
- <Card
101
- className="relative overflow-hidden ring-0 text-white backdrop-blur-md"
102
- style={{
103
- border: '2px solid transparent',
104
- background: `linear-gradient(315deg, color-mix(in oklab, ${accentVar} 70%, transparent), color-mix(in oklab, ${accentVar} 40%, transparent)) padding-box, linear-gradient(oklch(0.18 0.02 260),oklch(0.18 0.02 260)) padding-box, linear-gradient(315deg, white, ${accentVar}) border-box`,
105
- }}
106
- >
98
+ <Card className="glass relative overflow-hidden ring-0">
107
99
  <CardHeader className="relative z-10 pb-1">
108
100
  <div className="flex items-center justify-between">
109
- <CardDescription className="text-sm font-semibold uppercase tracking-wide text-white drop-shadow-sm">
101
+ <CardDescription className="text-sm font-medium uppercase tracking-wide">
110
102
  {label}
111
103
  </CardDescription>
112
- <div className="flex size-8 items-center justify-center rounded-md bg-white/20 backdrop-blur-sm">
113
- <Icon className="size-4 drop-shadow-sm" />
104
+ <div className="flex size-8 items-center justify-center rounded-md bg-muted text-muted-foreground">
105
+ <Icon className="size-4" />
114
106
  </div>
115
107
  </div>
116
108
  </CardHeader>
@@ -118,13 +110,20 @@ function KpiCard({
118
110
  {loading ? (
119
111
  <Skeleton className="h-9 w-24 rounded-md" />
120
112
  ) : (
121
- <p className="text-4xl font-extrabold tabular-nums drop-shadow-sm">{value}</p>
113
+ <p className="font-mono text-5xl font-bold tabular-nums tracking-tight text-card-foreground">
114
+ {value}
115
+ </p>
122
116
  )}
123
117
  <div className="mt-1.5">
124
118
  {loading ? (
125
119
  <Skeleton className="h-5 w-32 rounded-md" />
126
120
  ) : (
127
- <span className="inline-flex items-center gap-1 text-xs font-medium text-white/90 drop-shadow-sm">
121
+ <span
122
+ className={cn(
123
+ 'inline-flex items-center gap-1 text-xs font-medium',
124
+ positive ? 'text-success' : 'text-destructive'
125
+ )}
126
+ >
128
127
  {positive ? <TrendingUp className="size-3" /> : <TrendingDown className="size-3" />}
129
128
  {change}
130
129
  </span>
@@ -214,25 +213,27 @@ export default function Dashboard() {
214
213
  <>
215
214
  <PageHeader title={t('dashboard.title')} />
216
215
 
217
- <ContentPanel variant="full">
218
- {/* Welcome banner */}
219
- <Card className="relative overflow-hidden">
220
- <div className="pointer-events-none absolute inset-0 bg-gradient-to-r from-primary/5 to-transparent" />
216
+ <ContentPanel variant="full" className="space-y-12">
217
+ {/* Welcome banner — the view's hero panel */}
218
+ <Card className="glass relative overflow-hidden ring-0">
219
+ <div className="pointer-events-none absolute inset-0 [background:radial-gradient(90%_140%_at_0%_0%,var(--glow-primary),transparent_55%)]" />
221
220
  <CardHeader className="relative">
222
221
  <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
223
222
  <div className="flex items-center gap-4">
224
- <div className="flex size-14 shrink-0 items-center justify-center rounded-full bg-primary/10 text-xl font-semibold text-primary">
223
+ <div className="glow-teal-subtle flex size-14 shrink-0 items-center justify-center rounded-full bg-primary/10 text-xl font-semibold text-primary ring-1 ring-primary/30">
225
224
  {initials}
226
225
  </div>
227
226
  <div>
228
- <CardTitle className="text-xl">
227
+ <CardTitle className="font-display text-3xl tracking-tight">
229
228
  {greeting}, {firstName}!
230
229
  </CardTitle>
231
- <CardDescription className="mt-0.5">{user?.email}</CardDescription>
230
+ <CardDescription className="mt-1">{user?.email}</CardDescription>
232
231
  </div>
233
232
  </div>
234
233
  <div className="flex items-center gap-3 sm:flex-col sm:items-end">
235
- <p className="text-xs text-muted-foreground">{formattedDate}</p>
234
+ <p className="rounded-full border border-border px-3 py-1.5 text-xs text-muted-foreground">
235
+ {formattedDate}
236
+ </p>
236
237
  </div>
237
238
  </div>
238
239
  </CardHeader>
@@ -250,8 +251,6 @@ export default function Dashboard() {
250
251
  }
251
252
  positive={stats ? stats.users.newThisMonth >= stats.users.newLastMonth : true}
252
253
  icon={Users}
253
- accentColor="bg-primary/10 text-primary"
254
- accentVar="var(--primary)"
255
254
  sparklineData={stats?.users.weeklySignups}
256
255
  loading={!stats}
257
256
  />
@@ -267,8 +266,6 @@ export default function Dashboard() {
267
266
  }
268
267
  positive={stats ? stats.users.newThisMonth >= stats.users.newLastMonth : true}
269
268
  icon={UserPlus}
270
- accentColor="bg-blue-500/10 text-blue-600 dark:text-blue-400"
271
- accentVar="#3b82f6"
272
269
  sparklineData={stats?.users.weeklySignups}
273
270
  loading={!stats}
274
271
  />
@@ -284,8 +281,6 @@ export default function Dashboard() {
284
281
  }
285
282
  positive={stats ? stats.users.activeThisMonth >= stats.users.activeLastMonth : true}
286
283
  icon={Activity}
287
- accentColor="bg-amber-500/10 text-amber-600 dark:text-amber-400"
288
- accentVar="var(--warning)"
289
284
  loading={!stats}
290
285
  />
291
286
  <KpiCard
@@ -296,8 +291,6 @@ export default function Dashboard() {
296
291
  }
297
292
  positive={true}
298
293
  icon={Globe}
299
- accentColor="bg-destructive/10 text-destructive"
300
- accentVar="var(--destructive)"
301
294
  loading={!stats}
302
295
  />
303
296
  </div>
@@ -287,7 +287,7 @@ export function DocsPage() {
287
287
  )}
288
288
  </header>
289
289
 
290
- <article className="prose prose-invert max-w-none">
290
+ <article className="prose dark:prose-invert max-w-none">
291
291
  <page.Component components={mdxComponents} />
292
292
  </article>
293
293
 
@@ -63,7 +63,7 @@ function VibecarbonCTAFooter() {
63
63
  }
64
64
  footerLinks={
65
65
  <>
66
- <div className="flex items-center gap-6">
66
+ <div className="flex flex-wrap items-center justify-center gap-x-6 gap-y-2">
67
67
  <a
68
68
  href="https://github.com/hyperformant/vibecarbon/"
69
69
  target="_blank"
@@ -65,7 +65,7 @@ export default function LegalRoute() {
65
65
  <p className="text-lg text-muted-foreground">{page.description}</p>
66
66
  </header>
67
67
 
68
- <article className="prose prose-invert max-w-none">
68
+ <article className="prose dark:prose-invert max-w-none">
69
69
  <page.Component />
70
70
  </article>
71
71
  </div>
@@ -102,7 +102,7 @@ if (process.env.NODE_ENV !== 'production' && portOffset !== 0) {
102
102
  // makes "" mean "not set", matching operator intent (and how the k8s Secret
103
103
  // path already skips empty values).
104
104
  const cleanedEnv = Object.fromEntries(
105
- Object.entries(process.env).filter(([, value]) => value !== ''),
105
+ Object.entries(process.env).filter(([, value]) => value !== '')
106
106
  );
107
107
 
108
108
  // Validate environment variables
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibecarbon",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Create and manage production-ready Vibecarbon applications",
5
5
  "type": "module",
6
6
  "bin": "./src/cli.js",
@@ -57,12 +57,12 @@
57
57
  "url": "https://github.com/hyperformant/vibecarbon.git"
58
58
  },
59
59
  "dependencies": {
60
- "@aws-sdk/client-s3": "^3.1058.0",
61
- "@clack/prompts": "^1.5.0",
62
- "@pulumi/hcloud": "^1.38.0",
63
- "@pulumi/pulumi": "^3.244.0",
60
+ "@aws-sdk/client-s3": "^3.1077.0",
61
+ "@clack/prompts": "^1.6.0",
62
+ "@pulumi/hcloud": "^1.39.0",
63
+ "@pulumi/pulumi": "^3.248.0",
64
64
  "bcryptjs": "^3.0.3",
65
- "undici": "^8.3.0",
65
+ "undici": "^8.5.0",
66
66
  "which": "^7.0.0"
67
67
  },
68
68
  "pnpm": {
@@ -81,18 +81,18 @@
81
81
  }
82
82
  },
83
83
  "devDependencies": {
84
- "@biomejs/biome": "^2.4.16",
84
+ "@biomejs/biome": "^2.5.1",
85
85
  "@semantic-release/changelog": "^6.0.3",
86
86
  "@semantic-release/git": "^10.0.1",
87
87
  "@types/better-sqlite3": "^7.6.13",
88
- "@vitest/coverage-v8": "^4.1.8",
88
+ "@vitest/coverage-v8": "^4.1.9",
89
89
  "autocannon": "^8.0.0",
90
- "better-sqlite3": "^12.10.0",
91
- "conventional-changelog-conventionalcommits": "^9.3.1",
92
- "semantic-release": "^25.0.3",
90
+ "better-sqlite3": "^12.11.1",
91
+ "conventional-changelog-conventionalcommits": "^10.2.0",
92
+ "semantic-release": "^25.0.5",
93
93
  "tsx": "^4.22.4",
94
- "vite": "^8.0.16",
95
- "vitest": "^4.1.8",
94
+ "vite": "^8.1.2",
95
+ "vitest": "^4.1.9",
96
96
  "zod": "^4.4.3"
97
97
  }
98
98
  }
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Orphan dev-session reclaim helpers for `vibecarbon up`.
3
+ *
4
+ * When a project port is in use, the most common cause is an orphaned dev
5
+ * session belonging to the SAME project — a previous `node scripts/dev.js`
6
+ * tree (its child Vite + tsx API servers) that outlived its parent and was
7
+ * reparented to init while still holding the port. Rather than dodge the
8
+ * conflict by bumping DEV_PORT_OFFSET (which leaves the runaway alive and
9
+ * drifts the project onto a new port band), we identify the process actually
10
+ * listening on the port and, if it belongs to this project's working
11
+ * directory, kill its whole process group so a clean restart can reuse the
12
+ * normal ports.
13
+ *
14
+ * Strict scoping is the safety contract: we kill a process only when its
15
+ * working directory resolves to the project dir (or a subdir). A process we
16
+ * can't introspect (different user → EACCES) is treated as foreign and never
17
+ * touched. On non-Unix platforms, or when `lsof`/`ps` are unavailable, the
18
+ * helpers degrade to no-ops and the caller falls back to offset-bumping.
19
+ */
20
+
21
+ import { execFileSync } from 'node:child_process';
22
+ import { readlinkSync } from 'node:fs';
23
+ import { resolve, sep } from 'node:path';
24
+
25
+ const isUnix = () => process.platform !== 'win32';
26
+
27
+ const RUN_OPTS = { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] };
28
+
29
+ /**
30
+ * PIDs listening on the given TCP port. Returns deduped positive integers,
31
+ * or [] on non-Unix, missing `lsof`, or any error.
32
+ * @param {number} port
33
+ * @returns {number[]}
34
+ */
35
+ export function findPortListeners(port) {
36
+ if (!isUnix()) return [];
37
+ try {
38
+ const out = execFileSync('lsof', ['-t', `-iTCP:${port}`, '-sTCP:LISTEN'], RUN_OPTS);
39
+ const pids = out
40
+ .trim()
41
+ .split('\n')
42
+ .map((line) => Number(line.trim()))
43
+ .filter((n) => Number.isInteger(n) && n > 0);
44
+ return [...new Set(pids)];
45
+ } catch {
46
+ return [];
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Resolved working directory of a process, or null if it can't be read
52
+ * (gone, foreign user / EACCES, or unsupported platform). Linux reads
53
+ * /proc/<pid>/cwd; other Unixes fall back to `lsof`.
54
+ * @param {number} pid
55
+ * @returns {string | null}
56
+ */
57
+ export function getProcessCwd(pid) {
58
+ if (!isUnix()) return null;
59
+ try {
60
+ return readlinkSync(`/proc/${pid}/cwd`);
61
+ } catch (err) {
62
+ // Different user owns the process — treat as foreign, never kill.
63
+ if (err && err.code === 'EACCES') return null;
64
+ // ENOENT (no /proc, e.g. macOS) → try lsof; other errors → null below.
65
+ }
66
+ try {
67
+ const out = execFileSync('lsof', ['-a', '-p', String(pid), '-d', 'cwd', '-Fn'], RUN_OPTS);
68
+ const nameLine = out.split('\n').find((line) => line.startsWith('n'));
69
+ return nameLine ? nameLine.slice(1) : null;
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Process group id (pgid) of a process, or null on failure.
77
+ * @param {number} pid
78
+ * @returns {number | null}
79
+ */
80
+ export function getProcessGroup(pid) {
81
+ if (!isUnix()) return null;
82
+ try {
83
+ const out = execFileSync('ps', ['-o', 'pgid=', '-p', String(pid)], RUN_OPTS);
84
+ const pgid = Number(out.trim());
85
+ return Number.isInteger(pgid) && pgid > 0 ? pgid : null;
86
+ } catch {
87
+ return null;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * True iff the process's working directory resolves to `projectCwd` or a
93
+ * subdirectory of it.
94
+ * @param {number} pid
95
+ * @param {string} projectCwd
96
+ * @returns {boolean}
97
+ */
98
+ export function isOwnedByProject(pid, projectCwd) {
99
+ const cwd = getProcessCwd(pid);
100
+ if (!cwd) return false;
101
+ const proc = resolve(cwd);
102
+ const root = resolve(projectCwd);
103
+ return proc === root || proc.startsWith(root + sep);
104
+ }
105
+
106
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
107
+
108
+ /** True if the process is still alive (signal 0 probe). */
109
+ function isAlive(pid) {
110
+ try {
111
+ process.kill(pid, 0);
112
+ return true;
113
+ } catch (err) {
114
+ // ESRCH → gone. EPERM → alive but not ours (shouldn't happen for an
115
+ // owned orphan) — report alive so we don't claim a false reclaim.
116
+ return !!err && err.code !== 'ESRCH';
117
+ }
118
+ }
119
+
120
+ /** Best-effort signal to a target; swallows ESRCH/EPERM races. */
121
+ function signal(target, sig) {
122
+ try {
123
+ process.kill(target, sig);
124
+ } catch {
125
+ // Already gone or not permitted — nothing more we can do.
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Reclaim a port held by this project's own orphaned dev session.
131
+ *
132
+ * Finds the listeners on `port`, keeps only those whose working directory
133
+ * belongs to `projectCwd`, and kills each owned holder's process group
134
+ * (SIGTERM, then SIGKILL after a grace period). Foreign holders are reported
135
+ * but never touched.
136
+ *
137
+ * @param {number} port
138
+ * @param {string} projectCwd
139
+ * @param {{ graceMs?: number, intervalMs?: number }} [opts]
140
+ * @returns {Promise<{ killed: number[], foreign: number[], freed: boolean }>}
141
+ */
142
+ export async function reclaimPort(port, projectCwd, { graceMs = 2000, intervalMs = 100 } = {}) {
143
+ const listeners = findPortListeners(port);
144
+ if (listeners.length === 0) return { killed: [], foreign: [], freed: false };
145
+
146
+ const owned = [];
147
+ const foreign = [];
148
+ for (const pid of listeners) {
149
+ if (isOwnedByProject(pid, projectCwd)) owned.push(pid);
150
+ else foreign.push(pid);
151
+ }
152
+ if (owned.length === 0) return { killed: [], foreign, freed: false };
153
+
154
+ // Prefer group-targeted kills so the whole dev.js → vite/tsx tree dies in
155
+ // one shot; fall back to the bare pid if we can't resolve a pgid.
156
+ const seenGroups = new Set();
157
+ const targets = [];
158
+ for (const pid of owned) {
159
+ const pgid = getProcessGroup(pid);
160
+ if (pgid) {
161
+ if (!seenGroups.has(pgid)) {
162
+ seenGroups.add(pgid);
163
+ targets.push(-pgid);
164
+ }
165
+ } else {
166
+ targets.push(pid);
167
+ }
168
+ }
169
+
170
+ for (const target of targets) signal(target, 'SIGTERM');
171
+
172
+ const deadline = Date.now() + graceMs;
173
+ while (owned.some(isAlive) && Date.now() < deadline) {
174
+ await sleep(Math.min(intervalMs, Math.max(0, deadline - Date.now())));
175
+ }
176
+
177
+ if (owned.some(isAlive)) {
178
+ for (const target of targets) signal(target, 'SIGKILL');
179
+ await sleep(intervalMs);
180
+ }
181
+
182
+ const killed = owned.filter((pid) => !isAlive(pid));
183
+ const freed = !findPortListeners(port).length;
184
+ return { killed, foreign, freed };
185
+ }
186
+
187
+ /**
188
+ * @typedef {{ name: string, port: number }} PortConflict
189
+ */
190
+
191
+ /**
192
+ * Reclaim each conflicting port held by this project's orphaned dev session,
193
+ * then report which conflicts remain (foreign or otherwise unrecoverable) so
194
+ * the caller can fall back to offset-bumping for just those.
195
+ *
196
+ * Each port is re-checked after reclaim because killing one orphan's process
197
+ * group can free a sibling port in the same dev tree.
198
+ *
199
+ * @param {PortConflict[]} conflicts
200
+ * @param {string} projectCwd
201
+ * @param {{
202
+ * reclaim?: (port: number, cwd: string) => Promise<{ killed: number[], foreign: number[], freed: boolean }>,
203
+ * recheck: (port: number) => Promise<boolean>,
204
+ * onReclaim?: (conflict: PortConflict, result: { killed: number[] }) => void,
205
+ * }} deps
206
+ * @returns {Promise<{ reclaimed: Array<PortConflict & { killed: number[] }>, remaining: PortConflict[] }>}
207
+ */
208
+ export async function reclaimOrphanPorts(conflicts, projectCwd, deps) {
209
+ const { reclaim = reclaimPort, recheck, onReclaim } = deps;
210
+ const reclaimed = [];
211
+ const remaining = [];
212
+ for (const conflict of conflicts) {
213
+ const result = await reclaim(conflict.port, projectCwd);
214
+ if (result.killed.length > 0) {
215
+ reclaimed.push({ ...conflict, killed: result.killed });
216
+ onReclaim?.(conflict, result);
217
+ }
218
+ if (await recheck(conflict.port)) remaining.push(conflict);
219
+ }
220
+ return { reclaimed, remaining };
221
+ }
package/src/up.js CHANGED
@@ -14,6 +14,7 @@ import { renderHelp } from './lib/cli/help.js';
14
14
  import { parseFlags } from './lib/cli/parse-flags.js';
15
15
  import { c, printBanner } from './lib/colors.js';
16
16
  import { runCommandThroughTaskLog } from './lib/command.js';
17
+ import { reclaimOrphanPorts } from './lib/orphan.js';
17
18
  import { detectPackageManager } from './lib/project.js';
18
19
  import { assertInProjectDir } from './lib/project-guard.js';
19
20
  import { VERSION } from './lib/version.js';
@@ -219,14 +220,28 @@ export async function run(args = []) {
219
220
  }
220
221
 
221
222
  if (conflicts.length > 0) {
222
- p.log.warn('Port conflicts detected:');
223
- for (const { name, port } of conflicts) {
224
- p.log.message(` ${c.dim(name.padEnd(20))} port ${c.bold(String(port))} is in use`);
225
- }
223
+ // First reclaim any ports held by THIS project's own orphaned dev session
224
+ // (a previous `dev.js` tree that outlived its parent and kept squatting on
225
+ // the ports). Killing the orphan lets us reuse the normal port band; only
226
+ // genuinely foreign conflicts fall through to offset-bumping.
227
+ const { remaining } = await reclaimOrphanPorts(conflicts, cwd, {
228
+ recheck: isPortInUse,
229
+ onReclaim: ({ name, port }, { killed }) =>
230
+ p.log.success(
231
+ `Reclaimed ${name} port ${c.bold(String(port))} from orphaned dev session (PID ${killed.join(', ')})`,
232
+ ),
233
+ });
226
234
 
227
- const offset = await findFreeOffset(cwd);
228
- setPortOffset(offset, cwd);
229
- p.log.success(`Applied DEV_PORT_OFFSET=${offset} to .env.local`);
235
+ if (remaining.length > 0) {
236
+ p.log.warn('Port conflicts detected (not owned by this project):');
237
+ for (const { name, port } of remaining) {
238
+ p.log.message(` ${c.dim(name.padEnd(20))} port ${c.bold(String(port))} is in use`);
239
+ }
240
+
241
+ const offset = await findFreeOffset(cwd);
242
+ setPortOffset(offset, cwd);
243
+ p.log.success(`Applied DEV_PORT_OFFSET=${offset} to .env.local`);
244
+ }
230
245
  }
231
246
 
232
247
  const pm = detectPackageManager(cwd);
@@ -1,29 +0,0 @@
1
- <svg width="6453" height="1012" viewBox="0 0 6453 1012" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <path d="M1351.98 758.92C1348.62 758.92 1345.62 756.818 1344.46 753.66L1165.91 263.842C1164.01 258.623 1167.88 253.102 1173.43 253.102H1290.39C1293.8 253.102 1296.84 255.271 1297.95 258.501L1417.67 606.681C1420.15 613.888 1430.34 613.876 1432.8 606.663L1551.59 258.519C1552.7 255.279 1555.74 253.102 1559.17 253.102H1673.15C1678.7 253.102 1682.57 258.623 1680.67 263.842L1502.12 753.66C1500.96 756.818 1497.96 758.92 1494.6 758.92H1351.98Z" fill="white"/>
3
- <path d="M1748.91 758.92C1744.49 758.92 1740.91 755.338 1740.91 750.92V261.102C1740.91 256.684 1744.49 253.102 1748.91 253.102H1858.13C1862.55 253.102 1866.13 256.684 1866.13 261.102V750.92C1866.13 755.338 1862.55 758.92 1858.13 758.92H1748.91ZM1803.03 187.04C1762.6 187.04 1733.02 157.46 1733.02 118.02C1733.02 78.58 1762.6 49 1803.03 49C1842.47 49 1873.04 78.58 1873.04 118.02C1873.04 157.46 1842.47 187.04 1803.03 187.04Z" fill="white"/>
4
- <path d="M2254 764.836C2186.91 764.836 2138.3 739.429 2106.13 702.222C2101.46 696.826 2092.13 698.971 2090.88 705.993L2082.63 752.323C2081.95 756.14 2078.63 758.92 2074.75 758.92H1980.01C1975.59 758.92 1972.01 755.338 1972.01 750.92V76.72C1972.01 72.3017 1975.59 68.72 1980.01 68.72H2089.23C2093.65 68.72 2097.23 72.3017 2097.23 76.72V298.558C2097.23 306.201 2107.28 309.773 2112.61 304.285C2144.95 270.932 2190.97 247.186 2254.99 247.186C2391.06 247.186 2490.64 343.814 2490.64 505.518C2490.64 661.306 2391.06 764.836 2254 764.836ZM2229.35 661.306C2310.2 661.306 2363.45 599.188 2363.45 506.504C2363.45 412.834 2310.2 350.716 2229.35 350.716C2148.5 350.716 2095.26 412.834 2095.26 505.518C2095.26 599.188 2148.5 661.306 2229.35 661.306Z" fill="white"/>
5
- <path d="M2817.33 764.836C2652.67 764.836 2552.1 662.292 2552.1 507.49C2552.1 350.716 2654.64 247.186 2807.47 247.186C2956.36 247.186 3057.92 341.842 3057.92 492.7C3057.92 503.334 3057.92 514.647 3057.35 526.639C3057.16 530.846 3053.66 534.112 3049.45 534.112H2689.15C2685.88 534.112 2683.24 536.761 2683.24 540.028C2683.24 617.922 2735.49 667.222 2811.42 667.222C2870.04 667.222 2912.35 641.829 2927.02 596.261C2928.14 592.793 2931.29 590.314 2934.94 590.314H3043.48C3048.44 590.314 3052.22 594.795 3051.14 599.64C3030.5 691.905 2944.22 764.836 2817.33 764.836ZM2686.87 439.185C2685.84 443.964 2689.6 448.33 2694.49 448.33H2922.6C2927.43 448.33 2931.17 444.061 2930.29 439.314C2918.7 376.993 2874.41 342.828 2809.44 342.828C2749.31 342.828 2699.92 378.645 2686.87 439.185Z" fill="white"/>
6
- <path d="M3374.61 764.836C3215.87 764.836 3119.24 665.25 3119.24 506.504C3119.24 349.73 3220.8 247.186 3378.56 247.186C3509.1 247.186 3592.93 316.338 3615.47 429.168C3616.44 434.016 3612.67 438.47 3607.73 438.47H3492.48C3488.71 438.47 3485.48 435.836 3484.52 432.199C3471.16 381.668 3433.43 350.716 3376.59 350.716C3297.71 350.716 3247.42 414.806 3247.42 506.504C3247.42 598.202 3296.72 661.306 3376.59 661.306C3434.41 661.306 3472.19 629.409 3485.53 577.881C3486.48 574.229 3489.71 571.58 3493.49 571.58H3607.82C3612.73 571.58 3616.49 575.983 3615.56 580.809C3593.73 694.688 3505.22 764.836 3374.61 764.836Z" fill="white"/>
7
- <path d="M3912.45 764.836C3778.36 764.836 3674.83 666.236 3674.83 506.504C3674.83 350.716 3778.36 247.186 3912.45 247.186C3979.76 247.186 4027.7 271.986 4060.38 309.906C4065.03 315.295 4074.31 313.151 4075.56 306.146L4083.83 259.699C4084.51 255.882 4087.83 253.102 4091.71 253.102H4186.45C4190.87 253.102 4194.45 256.684 4194.45 261.102V750.92C4194.45 755.338 4190.87 758.92 4186.45 758.92H4091.72C4087.84 758.92 4084.51 756.13 4083.84 752.304L4075.41 704.325C4074.2 697.4 4065.13 695.195 4060.49 700.478C4027.28 738.288 3980.02 764.836 3912.45 764.836ZM3937.1 661.306C4017.96 661.306 4070.21 599.188 4070.21 507.49C4070.21 412.834 4017.96 350.716 3937.1 350.716C3855.27 350.716 3803.01 412.834 3803.01 506.504C3803.01 599.188 3855.27 661.306 3937.1 661.306Z" fill="white"/>
8
- <path d="M4308.28 758.92C4303.86 758.92 4300.28 755.338 4300.28 750.92V261.102C4300.28 256.684 4303.86 253.102 4308.28 253.102H4402.87C4406.82 253.102 4410.17 255.975 4410.78 259.87L4418.21 307.536C4419.35 314.889 4429.31 316.992 4433.87 311.112C4460.35 276.986 4499.34 253.102 4569.45 253.102H4588.08C4592.49 253.102 4596.08 256.684 4596.08 261.102V359.478C4596.08 363.896 4592.49 367.478 4588.08 367.478H4543.82C4456.06 367.478 4425.5 430.582 4425.5 508.476V750.92C4425.5 755.338 4421.92 758.92 4417.5 758.92H4308.28Z" fill="white"/>
9
- <path d="M4945.28 764.836C4878.19 764.836 4829.58 739.429 4797.41 702.222C4792.74 696.826 4783.41 698.971 4782.16 705.993L4773.91 752.323C4773.23 756.14 4769.91 758.92 4766.03 758.92H4671.29C4666.87 758.92 4663.29 755.338 4663.29 750.92V76.72C4663.29 72.3017 4666.87 68.72 4671.29 68.72H4780.51C4784.93 68.72 4788.51 72.3017 4788.51 76.72V298.558C4788.51 306.201 4798.56 309.773 4803.88 304.285C4836.23 270.932 4882.25 247.186 4946.27 247.186C5082.34 247.186 5181.92 343.814 5181.92 505.518C5181.92 661.306 5082.34 764.836 4945.28 764.836ZM4920.63 661.306C5001.48 661.306 5054.73 599.188 5054.73 506.504C5054.73 412.834 5001.48 350.716 4920.63 350.716C4839.78 350.716 4786.54 412.834 4786.54 505.518C4786.54 599.188 4839.78 661.306 4920.63 661.306Z" fill="white"/>
10
- <path d="M5506.64 764.836C5348.88 764.836 5243.38 661.306 5243.38 506.504C5243.38 350.716 5348.88 247.186 5506.64 247.186C5664.4 247.186 5769.9 350.716 5769.9 506.504C5769.9 661.306 5664.4 764.836 5506.64 764.836ZM5506.64 661.306C5589.46 661.306 5641.72 597.216 5641.72 506.504C5641.72 414.806 5589.46 350.716 5506.64 350.716C5423.82 350.716 5371.56 414.806 5371.56 506.504C5371.56 597.216 5423.82 661.306 5506.64 661.306Z" fill="white"/>
11
- <path d="M5861.42 758.92C5857 758.92 5853.42 755.338 5853.42 750.92V261.102C5853.42 256.684 5857 253.102 5861.42 253.102H5956.19C5960.05 253.102 5963.37 255.867 5964.06 259.671L5971.55 300.893C5972.78 307.645 5981.5 309.909 5986.22 304.93C6017.56 271.903 6062.75 247.186 6133.44 247.186C6243.88 247.186 6336.56 306.346 6336.56 477.91V750.92C6336.56 755.338 6332.98 758.92 6328.56 758.92H6219.34C6214.92 758.92 6211.34 755.338 6211.34 750.92V487.77C6211.34 398.044 6173.87 351.702 6100.9 351.702C6021.04 351.702 5978.64 401.988 5978.64 492.7V750.92C5978.64 755.338 5975.06 758.92 5970.64 758.92H5861.42Z" fill="white"/>
12
- <g clip-path="url(#clip0_77_150)">
13
- <path fill-rule="evenodd" clip-rule="evenodd" d="M434.346 2.3064C436.82 0.879051 439.867 0.879058 442.342 2.30641L872.685 250.582C875.162 252.01 876.688 254.652 876.688 257.511V754.051C876.688 756.91 875.162 759.552 872.685 760.981L442.342 1009.26C439.867 1010.68 436.82 1010.68 434.346 1009.26L4.00221 760.981C1.52575 759.552 0 756.91 0 754.051V257.511C0 254.652 1.52576 252.01 4.00222 250.582L434.346 2.3064ZM708.094 505.781C708.094 577.323 679.674 645.935 629.086 696.523C578.498 747.111 509.886 775.531 438.344 775.531C366.802 775.531 298.19 747.111 247.602 696.523C197.014 645.935 168.594 577.323 168.594 505.781C168.594 434.239 197.014 365.627 247.602 315.039C298.19 264.451 366.802 236.031 438.344 236.031C509.886 236.031 578.498 264.451 629.086 315.039C679.674 365.627 708.094 434.239 708.094 505.781Z" fill="url(#paint0_linear_77_150)"/>
14
- <path d="M876.688 252.891V472.062L438.344 606.938L0 472.062V252.891L438.344 0L876.688 252.891Z" fill="url(#paint1_linear_77_150)"/>
15
- </g>
16
- <defs>
17
- <linearGradient id="paint0_linear_77_150" x1="0" y1="0" x2="0" y2="1011.56" gradientUnits="userSpaceOnUse">
18
- <stop stop-color="#5EEAD4"/>
19
- <stop offset="1" stop-color="#0D9488"/>
20
- </linearGradient>
21
- <linearGradient id="paint1_linear_77_150" x1="175.338" y1="-5.42644e-06" x2="516.194" y2="492.348" gradientUnits="userSpaceOnUse">
22
- <stop stop-color="white" stop-opacity="0.5"/>
23
- <stop offset="1" stop-color="white" stop-opacity="0"/>
24
- </linearGradient>
25
- <clipPath id="clip0_77_150">
26
- <rect width="876.688" height="1011.56" fill="white"/>
27
- </clipPath>
28
- </defs>
29
- </svg>
@@ -1,29 +0,0 @@
1
- <svg width="6453" height="1012" viewBox="0 0 6453 1012" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <path d="M1351.98 758.92C1348.62 758.92 1345.62 756.818 1344.46 753.66L1165.91 263.842C1164.01 258.623 1167.88 253.102 1173.43 253.102H1290.39C1293.8 253.102 1296.84 255.271 1297.95 258.501L1417.67 606.681C1420.15 613.888 1430.34 613.876 1432.8 606.663L1551.59 258.519C1552.7 255.279 1555.74 253.102 1559.17 253.102H1673.15C1678.7 253.102 1682.57 258.623 1680.67 263.842L1502.12 753.66C1500.96 756.818 1497.96 758.92 1494.6 758.92H1351.98Z" fill="black"/>
3
- <path d="M1748.91 758.92C1744.49 758.92 1740.91 755.338 1740.91 750.92V261.102C1740.91 256.684 1744.49 253.102 1748.91 253.102H1858.13C1862.55 253.102 1866.13 256.684 1866.13 261.102V750.92C1866.13 755.338 1862.55 758.92 1858.13 758.92H1748.91ZM1803.03 187.04C1762.6 187.04 1733.02 157.46 1733.02 118.02C1733.02 78.58 1762.6 49 1803.03 49C1842.47 49 1873.04 78.58 1873.04 118.02C1873.04 157.46 1842.47 187.04 1803.03 187.04Z" fill="black"/>
4
- <path d="M2254 764.836C2186.91 764.836 2138.3 739.429 2106.13 702.222C2101.46 696.826 2092.13 698.971 2090.88 705.993L2082.63 752.323C2081.95 756.14 2078.63 758.92 2074.75 758.92H1980.01C1975.59 758.92 1972.01 755.338 1972.01 750.92V76.72C1972.01 72.3017 1975.59 68.72 1980.01 68.72H2089.23C2093.65 68.72 2097.23 72.3017 2097.23 76.72V298.558C2097.23 306.201 2107.28 309.773 2112.61 304.285C2144.95 270.932 2190.97 247.186 2254.99 247.186C2391.06 247.186 2490.64 343.814 2490.64 505.518C2490.64 661.306 2391.06 764.836 2254 764.836ZM2229.35 661.306C2310.2 661.306 2363.45 599.188 2363.45 506.504C2363.45 412.834 2310.2 350.716 2229.35 350.716C2148.5 350.716 2095.26 412.834 2095.26 505.518C2095.26 599.188 2148.5 661.306 2229.35 661.306Z" fill="black"/>
5
- <path d="M2817.33 764.836C2652.67 764.836 2552.1 662.292 2552.1 507.49C2552.1 350.716 2654.64 247.186 2807.47 247.186C2956.36 247.186 3057.92 341.842 3057.92 492.7C3057.92 503.334 3057.92 514.647 3057.35 526.639C3057.16 530.846 3053.66 534.112 3049.45 534.112H2689.15C2685.88 534.112 2683.24 536.761 2683.24 540.028C2683.24 617.922 2735.49 667.222 2811.42 667.222C2870.04 667.222 2912.35 641.829 2927.02 596.261C2928.14 592.793 2931.29 590.314 2934.94 590.314H3043.48C3048.44 590.314 3052.22 594.795 3051.14 599.64C3030.5 691.905 2944.22 764.836 2817.33 764.836ZM2686.87 439.185C2685.84 443.964 2689.6 448.33 2694.49 448.33H2922.6C2927.43 448.33 2931.17 444.061 2930.29 439.314C2918.7 376.993 2874.41 342.828 2809.44 342.828C2749.31 342.828 2699.92 378.645 2686.87 439.185Z" fill="black"/>
6
- <path d="M3374.61 764.836C3215.87 764.836 3119.24 665.25 3119.24 506.504C3119.24 349.73 3220.8 247.186 3378.56 247.186C3509.1 247.186 3592.93 316.338 3615.47 429.168C3616.44 434.016 3612.67 438.47 3607.73 438.47H3492.48C3488.71 438.47 3485.48 435.836 3484.52 432.199C3471.16 381.668 3433.43 350.716 3376.59 350.716C3297.71 350.716 3247.42 414.806 3247.42 506.504C3247.42 598.202 3296.72 661.306 3376.59 661.306C3434.41 661.306 3472.19 629.409 3485.53 577.881C3486.48 574.229 3489.71 571.58 3493.49 571.58H3607.82C3612.73 571.58 3616.49 575.983 3615.56 580.809C3593.73 694.688 3505.22 764.836 3374.61 764.836Z" fill="black"/>
7
- <path d="M3912.45 764.836C3778.36 764.836 3674.83 666.236 3674.83 506.504C3674.83 350.716 3778.36 247.186 3912.45 247.186C3979.76 247.186 4027.7 271.986 4060.38 309.905C4065.03 315.295 4074.31 313.151 4075.56 306.146L4083.83 259.699C4084.51 255.882 4087.83 253.102 4091.71 253.102H4186.45C4190.87 253.102 4194.45 256.684 4194.45 261.102V750.92C4194.45 755.338 4190.87 758.92 4186.45 758.92H4091.72C4087.84 758.92 4084.51 756.13 4083.84 752.304L4075.41 704.325C4074.2 697.4 4065.13 695.195 4060.49 700.478C4027.28 738.288 3980.02 764.836 3912.45 764.836ZM3937.1 661.306C4017.96 661.306 4070.21 599.188 4070.21 507.49C4070.21 412.834 4017.96 350.716 3937.1 350.716C3855.27 350.716 3803.01 412.834 3803.01 506.504C3803.01 599.188 3855.27 661.306 3937.1 661.306Z" fill="black"/>
8
- <path d="M4308.28 758.92C4303.86 758.92 4300.28 755.338 4300.28 750.92V261.102C4300.28 256.684 4303.86 253.102 4308.28 253.102H4402.87C4406.81 253.102 4410.17 255.975 4410.78 259.87L4418.21 307.536C4419.35 314.889 4429.31 316.992 4433.87 311.112C4460.35 276.986 4499.34 253.102 4569.45 253.102H4588.08C4592.49 253.102 4596.08 256.684 4596.08 261.102V359.478C4596.08 363.896 4592.49 367.478 4588.08 367.478H4543.82C4456.06 367.478 4425.5 430.582 4425.5 508.476V750.92C4425.5 755.338 4421.92 758.92 4417.5 758.92H4308.28Z" fill="black"/>
9
- <path d="M4945.28 764.836C4878.19 764.836 4829.58 739.429 4797.41 702.221C4792.74 696.826 4783.41 698.971 4782.16 705.993L4773.91 752.323C4773.23 756.14 4769.91 758.92 4766.03 758.92H4671.29C4666.87 758.92 4663.29 755.338 4663.29 750.92V76.72C4663.29 72.3017 4666.87 68.72 4671.29 68.72H4780.51C4784.93 68.72 4788.51 72.3017 4788.51 76.72V298.558C4788.51 306.201 4798.56 309.773 4803.88 304.285C4836.23 270.932 4882.25 247.186 4946.27 247.186C5082.34 247.186 5181.92 343.814 5181.92 505.518C5181.92 661.306 5082.34 764.836 4945.28 764.836ZM4920.63 661.306C5001.48 661.306 5054.73 599.188 5054.73 506.504C5054.73 412.834 5001.48 350.716 4920.63 350.716C4839.78 350.716 4786.54 412.834 4786.54 505.518C4786.54 599.188 4839.78 661.306 4920.63 661.306Z" fill="black"/>
10
- <path d="M5506.64 764.836C5348.88 764.836 5243.38 661.306 5243.38 506.504C5243.38 350.716 5348.88 247.186 5506.64 247.186C5664.4 247.186 5769.9 350.716 5769.9 506.504C5769.9 661.306 5664.4 764.836 5506.64 764.836ZM5506.64 661.306C5589.46 661.306 5641.72 597.216 5641.72 506.504C5641.72 414.806 5589.46 350.716 5506.64 350.716C5423.82 350.716 5371.56 414.806 5371.56 506.504C5371.56 597.216 5423.82 661.306 5506.64 661.306Z" fill="black"/>
11
- <path d="M5861.42 758.92C5857 758.92 5853.42 755.338 5853.42 750.92V261.102C5853.42 256.684 5857 253.102 5861.42 253.102H5956.19C5960.05 253.102 5963.37 255.867 5964.06 259.671L5971.55 300.893C5972.78 307.645 5981.5 309.909 5986.22 304.93C6017.56 271.903 6062.75 247.186 6133.44 247.186C6243.88 247.186 6336.56 306.346 6336.56 477.91V750.92C6336.56 755.338 6332.98 758.92 6328.56 758.92H6219.34C6214.92 758.92 6211.34 755.338 6211.34 750.92V487.77C6211.34 398.044 6173.87 351.702 6100.9 351.702C6021.04 351.702 5978.64 401.988 5978.64 492.7V750.92C5978.64 755.338 5975.06 758.92 5970.64 758.92H5861.42Z" fill="black"/>
12
- <g clip-path="url(#clip0_82_53)">
13
- <path fill-rule="evenodd" clip-rule="evenodd" d="M434.346 2.3064C436.82 0.879051 439.867 0.879058 442.342 2.30641L872.685 250.582C875.162 252.01 876.688 254.652 876.688 257.511V754.051C876.688 756.91 875.162 759.552 872.685 760.981L442.342 1009.26C439.867 1010.68 436.82 1010.68 434.346 1009.26L4.00221 760.981C1.52575 759.552 0 756.91 0 754.051V257.511C0 254.652 1.52576 252.01 4.00222 250.582L434.346 2.3064ZM708.094 505.781C708.094 577.323 679.674 645.935 629.086 696.523C578.498 747.111 509.886 775.531 438.344 775.531C366.802 775.531 298.19 747.111 247.602 696.523C197.014 645.935 168.594 577.323 168.594 505.781C168.594 434.239 197.014 365.627 247.602 315.039C298.19 264.451 366.802 236.031 438.344 236.031C509.886 236.031 578.498 264.451 629.086 315.039C679.674 365.627 708.094 434.239 708.094 505.781Z" fill="url(#paint0_linear_82_53)"/>
14
- <path d="M876.688 252.891V472.062L438.344 606.938L0 472.062V252.891L438.344 0L876.688 252.891Z" fill="url(#paint1_linear_82_53)"/>
15
- </g>
16
- <defs>
17
- <linearGradient id="paint0_linear_82_53" x1="0" y1="0" x2="0" y2="1011.56" gradientUnits="userSpaceOnUse">
18
- <stop stop-color="#5EEAD4"/>
19
- <stop offset="1" stop-color="#0D9488"/>
20
- </linearGradient>
21
- <linearGradient id="paint1_linear_82_53" x1="175.338" y1="-5.42644e-06" x2="516.194" y2="492.348" gradientUnits="userSpaceOnUse">
22
- <stop stop-color="white" stop-opacity="0.5"/>
23
- <stop offset="1" stop-color="white" stop-opacity="0"/>
24
- </linearGradient>
25
- <clipPath id="clip0_82_53">
26
- <rect width="876.688" height="1011.56" fill="white"/>
27
- </clipPath>
28
- </defs>
29
- </svg>