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