pricing-section.tsx385 lines · main
1import Link from 'next/link';
2
3interface Tier {
4 id: 'free' | 'pro' | 'team';
5 name: string;
6 price: string;
7 cadence: string;
8 pitch: string;
9 included: readonly { label: string; value: string }[];
10 overage: readonly { label: string; value: string }[];
11 features: readonly string[];
12 cta: { label: string; href: string };
13 highlight: boolean;
14 note?: string;
15}
16
17/**
18 * Pricing copy for briven.tech.
19 *
20 * Model: PREPAID, never metered. Each tier has hard caps at the included
21 * limits; hit a limit and we ask you to upgrade — we never bill beyond what
22 * you've loaded (no surprise bills). Paid top-ups run through mavi-pay.
23 * Limits are tuned to beat Neon/Supabase on room-per-euro (see pricing research
24 * 2026-06-15): Briven free = 1 GB + never pauses (Supabase free pauses at 1 wk,
25 * gives 500 MB); Briven Pro = 50 GB db vs Supabase Pro 8 GB; Team €99 vs $599.
26 */
27// NOTE: tier prices (€0/29/99) approved by Jürgen 2026-06-15; limits competitor-
28// tuned. Paid CTAs hit /dashboard/billing/upgrade?tier=… → Polar checkout when
29// BRIVEN_POLAR_PRO/TEAM_PRODUCT_ID + ACCESS_TOKEN are set on the API.
30const TIERS: readonly Tier[] = [
31 {
32 id: 'free',
33 name: 'free',
34 price: '€0',
35 cadence: '/month',
36 pitch: 'real projects, no credit card, never pauses. perfect for trying briven or a small database.',
37 included: [
38 { label: 'projects', value: '3' },
39 { label: 'database size', value: '1 gb' },
40 { label: 'file storage', value: '1 gb' },
41 { label: 'history / undo', value: '7 days' },
42 { label: 'logins / users', value: '50,000' },
43 { label: 'live updates', value: '10 at once' },
44 ],
45 overage: [],
46 features: [
47 'no credit card needed',
48 'never pauses (unlike the big names)',
49 'community support (discord, github)',
50 ],
51 cta: { label: 'get started free', href: '/signin' },
52 highlight: false,
53 },
54 {
55 id: 'pro',
56 name: 'pro',
57 price: '€29',
58 cadence: '/month',
59 pitch: 'growing apps. 50 gb database — over 6× what Supabase Pro gives you.',
60 included: [
61 { label: 'projects', value: '10' },
62 { label: 'database size', value: '50 gb' },
63 { label: 'file storage', value: '100 gb' },
64 { label: 'history / undo', value: '30 days' },
65 { label: 'logins / users', value: '100,000' },
66 { label: 'live updates', value: '100 at once' },
67 ],
68 overage: [],
69 features: [
70 'point-and-click + spreadsheet editing',
71 'custom domains per project',
72 'daily backups (30-day history)',
73 'email support (48h)',
74 ],
75 cta: { label: 'upgrade to pro', href: '/dashboard/billing/upgrade?tier=pro' },
76 highlight: true,
77 },
78 {
79 id: 'team',
80 name: 'team',
81 price: '€99',
82 cadence: '/month',
83 pitch: 'teams + bigger workloads. €99 where Supabase charges $599 for their team plan.',
84 included: [
85 { label: 'projects', value: 'unlimited' },
86 { label: 'database size', value: '500 gb' },
87 { label: 'file storage', value: '500 gb' },
88 { label: 'history / undo', value: '1 year' },
89 { label: 'logins / users', value: 'unlimited' },
90 { label: 'live updates', value: '500 at once' },
91 ],
92 overage: [],
93 features: [
94 '5 team seats included',
95 'hourly backups',
96 'priority support',
97 'audit log',
98 ],
99 cta: { label: 'upgrade to team', href: '/dashboard/billing/upgrade?tier=team' },
100 highlight: false,
101 },
102];
103
104export function PricingSection() {
105 return (
106 <section id="pricing" className="relative z-10 mx-auto w-full max-w-6xl px-6 py-20 sm:py-24">
107 <div className="flex flex-col gap-3 pb-10">
108 <h2 className="font-sans font-medium tracking-[-0.02em] text-[var(--color-text)] text-[var(--text-h1)]">
109 pricing
110 </h2>
111 <p className="max-w-2xl font-sans leading-[1.6] text-[var(--color-text-muted)] text-[var(--text-body)]">
112 simple, honest pricing — far more room than Neon or Supabase, at a fraction of the price.
113 the free tier needs no card and never pauses. paid plans are prepaid — you load credit and
114 only spend what you put in, so there are no surprise bills. cancel any time, and export
115 your data whenever you like — it&apos;s your database.
116 </p>
117 </div>
118
119 <div className="grid grid-cols-1 gap-px overflow-hidden rounded-[var(--radius-lg)] border border-[var(--color-border-subtle)] bg-[var(--color-border-subtle)] lg:grid-cols-3">
120 {TIERS.map((tier) => (
121 <TierCard key={tier.id} tier={tier} />
122 ))}
123 </div>
124
125 <div className="flex flex-col gap-1 pt-6 font-mono text-[var(--color-text-subtle)] text-[var(--text-xs)]">
126 <p>
127 prices in EUR · vat added at checkout for EU customers (reverse-charge for valid vat id) ·
128 free tier needs no card · paid is prepaid — no surprise bills
129 </p>
130 <p>
131 self-hosting is free forever under agpl-3.0 · hit a plan limit and we ask you to upgrade —
132 we never bill you beyond what you&apos;ve loaded
133 </p>
134 </div>
135
136 <SelfHostCommercialBand />
137 </section>
138 );
139}
140
141interface CommercialTier {
142 name: string;
143 price: string;
144 cadence: string;
145 who: string;
146 bullets: readonly string[];
147}
148
149const COMMERCIAL_TIERS: readonly CommercialTier[] = [
150 {
151 name: 'startup',
152 price: '€400',
153 cadence: '/month',
154 who: 'early-stage companies past the agpl-acceptable threshold',
155 bullets: [
156 'commercial licence for one production deployment',
157 'up to €5M ARR',
158 'community + email support',
159 ],
160 },
161 {
162 name: 'business',
163 price: '€2,000',
164 cadence: '/month',
165 who: 'scale-ups, agencies, multi-product orgs',
166 bullets: [
167 'up to 10 production deployments',
168 'up to €25M ARR',
169 'email support, 5×8 response SLA',
170 ],
171 },
172 {
173 name: 'enterprise',
174 price: 'from €8,000',
175 cadence: '/month',
176 who: 'regulated industries, large internal IT',
177 bullets: [
178 'unlimited deployments',
179 'custom SLA + dedicated account',
180 'optional source escrow, priority security patches',
181 ],
182 },
183];
184
185function SelfHostCommercialBand() {
186 return (
187 <section
188 id="self-host-commercial"
189 className="mt-16 flex flex-col gap-6 border-t border-[var(--color-border-subtle)] pt-12"
190 >
191 <div className="flex flex-col gap-4">
192 <p className="font-mono uppercase tracking-[0.12em] text-[var(--color-primary)] text-[var(--text-xs)]">
193 self-host commercial
194 </p>
195 <h2 className="font-sans font-medium tracking-[-0.02em] text-[var(--color-text)] text-[var(--text-h2)]">
196 run briven on your own hardware.
197 </h2>
198 <p className="max-w-2xl font-sans leading-[1.6] text-[var(--color-text-muted)] text-[var(--text-body)]">
199 briven-core ships under <strong>agpl-3.0</strong> and is free to run, modify, and
200 self-host forever. the commercial licence is the alternative for companies that need to
201 embed briven in a product without releasing their source, run it as a hosted service for
202 third parties, or operate it inside a legal entity over <strong>€10M ARR</strong> or{' '}
203 <strong>100 FTEs</strong>. see the{' '}
204 <Link
205 href="https://docs.briven.tech/trust"
206 className="text-[var(--color-text-link)] underline-offset-2 hover:underline"
207 >
208 trust page
209 </Link>{' '}
210 for full terms.
211 </p>
212 </div>
213
214 <div className="grid grid-cols-1 gap-px overflow-hidden rounded-[var(--radius-lg)] border border-[var(--color-border-subtle)] bg-[var(--color-border-subtle)] lg:grid-cols-3">
215 {COMMERCIAL_TIERS.map((tier) => (
216 <CommercialTierCard key={tier.name} tier={tier} />
217 ))}
218 </div>
219
220 <div className="flex flex-col gap-1 font-mono text-[var(--color-text-subtle)] text-[var(--text-xs)]">
221 <p>
222 all commercial tiers billed annually in advance · prices in EUR exclusive of VAT · the
223 agpl-3.0 release stays free regardless of the commercial tier table
224 </p>
225 <p>
226 source available at{' '}
227 <Link
228 href="https://codeberg.org/flndrn/briven"
229 className="hover:text-[var(--color-text-muted)]"
230 >
231 codeberg.org/flndrn/briven
232 </Link>{' '}
233 · sla details at{' '}
234 <Link
235 href="https://docs.briven.tech/sla"
236 className="hover:text-[var(--color-text-muted)]"
237 >
238 docs.briven.tech/sla
239 </Link>
240 </p>
241 </div>
242 </section>
243 );
244}
245
246function CommercialTierCard({ tier }: { tier: CommercialTier }) {
247 return (
248 <article className="flex flex-col gap-5 bg-[var(--color-bg)] p-8">
249 <header>
250 <h3 className="font-mono uppercase tracking-[0.12em] text-[var(--color-primary)] text-[var(--text-xs)]">
251 {tier.name}
252 </h3>
253 </header>
254
255 <div className="flex items-baseline gap-1">
256 <span className="font-sans font-medium tracking-[-0.02em] text-[var(--color-text)] text-[var(--text-display-3)]">
257 {tier.price}
258 </span>
259 <span className="font-mono text-[var(--color-text-subtle)] text-[var(--text-small)]">
260 {tier.cadence}
261 </span>
262 </div>
263
264 <p className="font-sans leading-[1.6] text-[var(--color-text-muted)] text-[var(--text-small)]">
265 {tier.who}
266 </p>
267
268 <ul className="flex flex-col gap-2 border-t border-[var(--color-border-subtle)] pt-5">
269 {tier.bullets.map((b) => (
270 <li
271 key={b}
272 className="flex items-start gap-2 font-mono text-[var(--color-text-muted)] text-[var(--text-xs)]"
273 >
274 <span
275 aria-hidden
276 className="mt-1 inline-block size-1 rounded-full bg-[var(--color-primary)]"
277 />
278 <span>{b}</span>
279 </li>
280 ))}
281 </ul>
282
283 <a
284 href="mailto:licensing@flndrn.com?subject=commercial%20licence%20enquiry"
285 className="mt-auto inline-flex h-11 items-center justify-center rounded-[var(--radius-md)] border border-[var(--color-border)] px-4 font-sans font-medium text-[var(--color-text)] transition-colors hover:border-[var(--color-border-strong)] hover:bg-[var(--color-surface-raised)]"
286 >
287 contact licensing
288 </a>
289 </article>
290 );
291}
292
293function TierCard({ tier }: { tier: Tier }) {
294 return (
295 <article
296 className={`flex flex-col gap-5 bg-[var(--color-bg)] p-8 ${
297 tier.highlight ? 'ring-1 ring-inset ring-[var(--color-border-primary)]' : ''
298 }`}
299 >
300 <header className="flex items-baseline justify-between">
301 <h3 className="font-mono uppercase tracking-[0.12em] text-[var(--color-primary)] text-[var(--text-xs)]">
302 {tier.name}
303 </h3>
304 {tier.highlight ? (
305 <span className="rounded-[var(--radius-full)] bg-[var(--color-primary-subtle)] px-2 py-0.5 font-mono text-[10px] uppercase tracking-wider text-[var(--color-primary)]">
306 most popular
307 </span>
308 ) : null}
309 </header>
310
311 <div className="flex items-baseline gap-1">
312 <span className="font-sans font-medium tracking-[-0.02em] text-[var(--color-text)] text-[var(--text-display-3)]">
313 {tier.price}
314 </span>
315 <span className="font-mono text-[var(--color-text-subtle)] text-[var(--text-small)]">
316 {tier.cadence}
317 </span>
318 </div>
319
320 <p className="font-sans leading-[1.6] text-[var(--color-text-muted)] text-[var(--text-small)]">
321 {tier.pitch}
322 </p>
323
324 <div className="flex flex-col gap-3">
325 <p className="font-mono text-[10px] uppercase tracking-[0.1em] text-[var(--color-text-subtle)]">
326 included each month
327 </p>
328 <dl className="grid grid-cols-1 gap-1.5 font-mono text-[var(--text-xs)]">
329 {tier.included.map((row) => (
330 <div key={row.label} className="flex justify-between gap-3">
331 <dt className="text-[var(--color-text-muted)]">{row.label}</dt>
332 <dd className="whitespace-nowrap text-right text-[var(--color-text)]">{row.value}</dd>
333 </div>
334 ))}
335 </dl>
336 </div>
337
338 {tier.overage.length > 0 ? (
339 <div className="flex flex-col gap-3">
340 <p className="font-mono text-[10px] uppercase tracking-[0.1em] text-[var(--color-text-subtle)]">
341 past the bucket
342 </p>
343 <dl className="grid grid-cols-1 gap-1.5 font-mono text-[var(--text-xs)]">
344 {tier.overage.map((row) => (
345 <div key={row.label} className="flex justify-between gap-3">
346 <dt className="text-[var(--color-text-subtle)]">{row.label}</dt>
347 <dd className="whitespace-nowrap text-right text-[var(--color-text-muted)]">
348 {row.value}
349 </dd>
350 </div>
351 ))}
352 </dl>
353 </div>
354 ) : null}
355
356 {tier.features.length > 0 ? (
357 <ul className="flex flex-col gap-2 border-t border-[var(--color-border-subtle)] pt-5">
358 {tier.features.map((feature) => (
359 <li
360 key={feature}
361 className="flex items-start gap-2 font-mono text-[var(--color-text-muted)] text-[var(--text-xs)]"
362 >
363 <span
364 aria-hidden
365 className="mt-1 inline-block size-1 rounded-full bg-[var(--color-primary)]"
366 />
367 <span>{feature}</span>
368 </li>
369 ))}
370 </ul>
371 ) : null}
372
373 <Link
374 href={tier.cta.href}
375 className={
376 tier.highlight
377 ? 'mt-auto inline-flex h-11 items-center justify-center rounded-[var(--radius-md)] bg-[var(--color-primary)] px-4 font-sans font-medium text-[var(--color-text-inverse)] shadow-[var(--shadow-sm)] transition-colors duration-[var(--duration-fast)] ease-[var(--ease-briven)] hover:bg-[var(--color-primary-hover)]'
378 : 'mt-auto inline-flex h-11 items-center justify-center rounded-[var(--radius-md)] border border-[var(--color-border)] bg-transparent px-4 font-sans font-medium text-[var(--color-text)] transition-colors duration-[var(--duration-fast)] ease-[var(--ease-briven)] hover:border-[var(--color-border-strong)] hover:bg-[var(--color-surface-raised)]'
379 }
380 >
381 {tier.cta.label}
382 </Link>
383 </article>
384 );
385}