Files
breakpilot-core/pitch-deck/components/slides/BusinessModelSlide.tsx
Benjamin Admin f66f32ee9d
All checks were successful
Build pitch-deck / build-push-deploy (push) Successful in 1m18s
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 34s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 32s
fix(pitch-deck): all financial slides now read from fp_* tables via useFpKPIs
New shared hook: useFpKPIs — loads annual KPIs from fp_guv/liquiditaet/personal/kunden.
Replaces useFinancialModel (simplified model) for KPI display on all slides.

Slides updated:
- CompetitionSlide: "110 Gesetze" → "380+ Regularien & Normen"
- BusinessModelSlide: ACV + Gross Margin from fp_* (was useFinancialModel)
- ExecutiveSummarySlide: Unternehmensentwicklung from fp_* (was useFinancialModel)
- FinancialsSlide: KPI cards from fp_* (ARR, Customers, Break-Even, EBIT 2030)

All slides now show consistent numbers from the same source of truth (fp_* tables).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 17:00:27 +02:00

173 lines
8.1 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 { useFpKPIs } from '@/lib/hooks/useFpKPIs'
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
preferredScenarioId?: string | null
isWandeldarlehen?: boolean
}
export default function BusinessModelSlide({ lang, isWandeldarlehen }: BusinessModelSlideProps) {
const i = t(lang)
const de = lang === 'de'
const { last } = useFpKPIs(isWandeldarlehen)
const finalCustomers = last?.customers || 0
const finalArr = last?.arr || 0
const acv = finalCustomers > 0 ? Math.round(finalArr / finalCustomers) : 0
const grossMargin = last?.grossMargin ?? 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,
},
]
// grossMargin already defined above from useFpKPIs
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>
)
}