pricing-section.tsx384 lines · main
| 1 | import Link from 'next/link'; |
| 2 | |
| 3 | interface 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). |
| 29 | const 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 | |
| 103 | export 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'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've loaded |
| 132 | </p> |
| 133 | </div> |
| 134 | |
| 135 | <SelfHostCommercialBand /> |
| 136 | </section> |
| 137 | ); |
| 138 | } |
| 139 | |
| 140 | interface CommercialTier { |
| 141 | name: string; |
| 142 | price: string; |
| 143 | cadence: string; |
| 144 | who: string; |
| 145 | bullets: readonly string[]; |
| 146 | } |
| 147 | |
| 148 | const 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 | |
| 184 | function 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 | |
| 245 | function 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 | |
| 292 | function 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 | } |