Files
breakpilot-core/pitch-deck/components/slides/BusinessModelSlide.tsx
Benjamin Admin 5264528940
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 38s
CI / test-python-voice (push) Successful in 41s
CI / test-bqas (push) Successful in 37s
Build pitch-deck / build-push-deploy (push) Failing after 47s
style(pitch-deck): highlight Professional tier with silver border on BusinessModel slide
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:36:02 +02:00

173 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { Language } from '@/lib/types'
import { t } from '@/lib/i18n'
import { useFinancialModel } from '@/lib/hooks/useFinancialModel'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import GlassCard from '../ui/GlassCard'
import { ArrowRight, TrendingUp, Target, Repeat, DollarSign, Users, BarChart3 } from 'lucide-react'
interface BusinessModelSlideProps {
lang: Language
products?: unknown[]
investorId?: string | null
}
export default function BusinessModelSlide({ lang, investorId }: BusinessModelSlideProps) {
const i = t(lang)
const de = lang === 'de'
const fm = useFinancialModel(investorId || null)
const summary = fm.activeResults?.summary
const results = fm.activeResults?.results || []
const lastResult = results[results.length - 1]
const finalCustomers = summary?.final_customers || 0
const finalArr = summary?.final_arr || 0
const acv = finalCustomers > 0 ? Math.round(finalArr / finalCustomers) : 0
const tiers = [
{
name: 'Starter',
target: de ? 'Startups & Kleinstunternehmen' : 'Startups & Micro',
employees: '< 10',
price: de ? '3.600' : '3,600',
period: de ? 'EUR/Jahr' : 'EUR/yr',
features: de
? ['Code Security (SAST/DAST)', 'Compliance-Dokumente', 'Consent Management', '1 Anwendung']
: ['Code Security (SAST/DAST)', 'Compliance documents', 'Consent management', '1 application'],
highlight: false,
},
{
name: 'Professional',
target: de ? 'KMU & Mittelstand' : 'SME & Mid-Market',
employees: '10 250',
price: de ? '15.000 40.000' : '15,000 40,000',
period: de ? 'EUR/Jahr' : 'EUR/yr',
features: de
? ['Alle Module inkl. CE-Bewertung', 'Audit Manager End-to-End', 'AI Act Compliance (UCCA)', 'Unbegrenzte Anwendungen']
: ['All modules incl. CE assessment', 'Audit Manager end-to-end', 'AI Act Compliance (UCCA)', 'Unlimited applications'],
highlight: true,
},
{
name: 'Enterprise',
target: de ? 'Konzerne & OEMs' : 'Enterprises & OEMs',
employees: '250+',
price: de ? 'ab 50.000' : 'from 50,000',
period: de ? 'EUR/Jahr' : 'EUR/yr',
features: de
? ['Dedizierte Instanz', 'Custom Integrationen (SAP, MES)', 'SLA & Priority Support', 'Tender Matching & RFQ-Prüfung']
: ['Dedicated instance', 'Custom integrations (SAP, MES)', 'SLA & priority support', 'Tender matching & RFQ verification'],
highlight: false,
},
]
const grossMargin = lastResult?.gross_margin_pct ?? 0
const acvLabel = acv > 0
? (de ? `${(acv / 1000).toFixed(1).replace('.', ',')}k EUR` : `EUR ${(acv / 1000).toFixed(1)}k`)
: '—'
const metrics = [
{ icon: DollarSign, color: 'text-indigo-400', label: 'ACV (2030)', value: acvLabel, sub: de ? 'Durchschnittlicher Vertragswert (berechnet)' : 'Average Contract Value (computed)' },
{ icon: TrendingUp, color: 'text-emerald-400', label: 'Gross Margin', value: `${Math.round(grossMargin)}%`, sub: de ? 'Cloud-native, keine Hardware-Kosten' : 'Cloud-native, no hardware costs' },
{ icon: Repeat, color: 'text-purple-400', label: 'NRR Ziel', value: '> 120%', sub: de ? 'Upsell: mehr Module, mehr Nutzer' : 'Upsell: more modules, more users' },
{ icon: Target, color: 'text-amber-400', label: 'Payback', value: de ? '< 3 Monate' : '< 3 Months', sub: de ? 'Ersparnis übersteigt Kosten ab Q1' : 'Savings exceed costs from Q1' },
]
return (
<div className="max-w-6xl mx-auto">
<FadeInView className="text-center mb-6">
<h2 className="text-4xl md:text-5xl font-bold mb-3">
<GradientText>{i.businessModel.title}</GradientText>
</h2>
<p className="text-lg text-white/50 max-w-2xl mx-auto">
{de ? 'Mitarbeiterbasiertes SaaS — Kunden sparen mehr als sie zahlen' : 'Employee-based SaaS — customers save more than they pay'}
</p>
</FadeInView>
<div className="grid md:grid-cols-12 gap-4">
{/* Left: Pricing Tiers */}
<div className="md:col-span-7">
<div className="grid grid-cols-3 gap-3">
{tiers.map((tier, idx) => (
<FadeInView key={idx} delay={0.1 + idx * 0.1}>
<GlassCard hover={false} className={`p-4 h-full ${tier.highlight ? 'border-slate-300/40 bg-gradient-to-b from-slate-200/[0.08] to-slate-400/[0.04] shadow-lg shadow-slate-300/10 ring-1 ring-slate-300/20' : ''}`}>
<h3 className="text-base font-bold text-white mb-0.5">{tier.name}</h3>
<p className="text-xs text-white/40 mb-2">{tier.target}</p>
<p className="text-xs text-white/30 mb-3">{tier.employees} {de ? 'Mitarbeiter' : 'employees'}</p>
<div className="mb-3">
<span className="text-xl font-black text-white">{tier.price}</span>
<span className="text-xs text-white/40 ml-1">{tier.period}</span>
</div>
<ul className="space-y-1.5">
{tier.features.map((f, i) => (
<li key={i} className="flex items-start gap-1.5 text-sm text-white/50">
<span className="w-1 h-1 rounded-full bg-indigo-400 mt-1.5 shrink-0" />
{f}
</li>
))}
</ul>
</GlassCard>
</FadeInView>
))}
</div>
{/* Expansion arrow */}
<FadeInView delay={0.4} className="flex items-center justify-center gap-2 mt-3">
<span className="text-[10px] text-white/30">Starter</span>
<ArrowRight className="w-3 h-3 text-indigo-400/40" />
<span className="text-[10px] text-white/30">Professional</span>
<ArrowRight className="w-3 h-3 text-indigo-400/40" />
<span className="text-[10px] text-white/30">Enterprise</span>
<span className="text-xs text-white/20 ml-2">{de ? 'Land & Expand' : 'Land & Expand'}</span>
</FadeInView>
</div>
{/* Right: Unit Economics */}
<div className="md:col-span-5">
<FadeInView delay={0.3}>
<GlassCard hover={false} className="p-4 h-full">
<h3 className="text-xs font-bold text-white/40 uppercase tracking-wider mb-4">
Unit Economics
</h3>
<div className="space-y-4">
{metrics.map((m, idx) => {
const Icon = m.icon
return (
<div key={idx} className="flex items-start gap-3">
<div className={`w-8 h-8 rounded-lg bg-white/[0.05] border border-white/10 flex items-center justify-center shrink-0`}>
<Icon className={`w-4 h-4 ${m.color}`} />
</div>
<div className="flex-1">
<div className="flex items-baseline justify-between">
<span className="text-xs text-white/40 uppercase tracking-wider">{m.label}</span>
<span className={`text-lg font-black ${m.color}`}>{m.value}</span>
</div>
<p className="text-[10px] text-white/30">{m.sub}</p>
</div>
</div>
)
})}
</div>
{/* Bottom-up sizing */}
<div className="mt-4 pt-3 border-t border-white/5">
<div className="flex items-center gap-1.5 mb-1">
<BarChart3 className="w-3 h-3 text-white/30" />
<span className="text-[10px] text-white/30 uppercase tracking-wider">Bottom-Up</span>
</div>
<p className="text-xs text-white/50">
{de
? `${finalCustomers.toLocaleString('de-DE')} Kunden × ${acv.toLocaleString('de-DE')} EUR ACV = ~${(finalArr / 1_000_000).toFixed(1).replace('.', ',')} Mio. EUR ARR (2030)`
: `${finalCustomers.toLocaleString('en-US')} customers × EUR ${acv.toLocaleString('en-US')} ACV = ~EUR ${(finalArr / 1_000_000).toFixed(1)}M ARR (2030)`}
</p>
</div>
</GlassCard>
</FadeInView>
</div>
</div>
</div>
)
}