feat: add pitch-deck service to core infrastructure

Migrated pitch-deck from breakpilot-pwa to breakpilot-core.
Container: bp-core-pitch-deck on port 3012.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Boenisch
2026-02-14 19:44:27 +01:00
parent 3739d2b8b9
commit f2a24d7341
68 changed files with 5911 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
export const fadeIn = {
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 },
transition: { duration: 0.5 },
}
export const fadeInUp = {
initial: { opacity: 0, y: 40 },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -20 },
transition: { duration: 0.6, ease: [0.22, 1, 0.36, 1] },
}
export const fadeInDown = {
initial: { opacity: 0, y: -40 },
animate: { opacity: 1, y: 0 },
transition: { duration: 0.6, ease: [0.22, 1, 0.36, 1] },
}
export const fadeInLeft = {
initial: { opacity: 0, x: -60 },
animate: { opacity: 1, x: 0 },
transition: { duration: 0.6, ease: [0.22, 1, 0.36, 1] },
}
export const fadeInRight = {
initial: { opacity: 0, x: 60 },
animate: { opacity: 1, x: 0 },
transition: { duration: 0.6, ease: [0.22, 1, 0.36, 1] },
}
export const scaleIn = {
initial: { opacity: 0, scale: 0.8 },
animate: { opacity: 1, scale: 1 },
transition: { duration: 0.5, ease: [0.22, 1, 0.36, 1] },
}
export const slideVariants = {
enter: (direction: number) => ({
x: direction > 0 ? '100%' : '-100%',
opacity: 0,
}),
center: {
x: 0,
opacity: 1,
},
exit: (direction: number) => ({
x: direction < 0 ? '100%' : '-100%',
opacity: 0,
}),
}
export const staggerContainer = {
animate: {
transition: {
staggerChildren: 0.1,
delayChildren: 0.2,
},
},
}
export const staggerItem = {
initial: { opacity: 0, y: 20 },
animate: { opacity: 1, y: 0 },
transition: { duration: 0.5 },
}

10
pitch-deck/lib/db.ts Normal file
View File

@@ -0,0 +1,10 @@
import { Pool } from 'pg'
const pool = new Pool({
connectionString: process.env.DATABASE_URL || 'postgres://breakpilot:breakpilot123@localhost:5432/breakpilot_db',
max: 5,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000,
})
export default pool

View File

@@ -0,0 +1,109 @@
'use client'
import { useState, useEffect, useCallback, useRef } from 'react'
import { FMScenario, FMResult, FMComputeResponse } from '../types'
export function useFinancialModel() {
const [scenarios, setScenarios] = useState<FMScenario[]>([])
const [activeScenarioId, setActiveScenarioId] = useState<string | null>(null)
const [compareMode, setCompareMode] = useState(false)
const [results, setResults] = useState<Map<string, FMComputeResponse>>(new Map())
const [loading, setLoading] = useState(true)
const [computing, setComputing] = useState(false)
const computeTimer = useRef<NodeJS.Timeout | null>(null)
// Load scenarios on mount
useEffect(() => {
async function load() {
try {
const res = await fetch('/api/financial-model')
if (res.ok) {
const data: FMScenario[] = await res.json()
setScenarios(data)
const defaultScenario = data.find(s => s.is_default) || data[0]
if (defaultScenario) {
setActiveScenarioId(defaultScenario.id)
}
}
} catch (err) {
console.error('Failed to load financial model:', err)
} finally {
setLoading(false)
}
}
load()
}, [])
// Compute when active scenario changes
useEffect(() => {
if (activeScenarioId && !results.has(activeScenarioId)) {
compute(activeScenarioId)
}
}, [activeScenarioId]) // eslint-disable-line react-hooks/exhaustive-deps
const compute = useCallback(async (scenarioId: string) => {
setComputing(true)
try {
const res = await fetch('/api/financial-model/compute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ scenarioId }),
})
if (res.ok) {
const data: FMComputeResponse = await res.json()
setResults(prev => new Map(prev).set(scenarioId, data))
}
} catch (err) {
console.error('Compute failed:', err)
} finally {
setComputing(false)
}
}, [])
const updateAssumption = useCallback(async (scenarioId: string, key: string, value: number | number[]) => {
// Optimistic update in local state
setScenarios(prev => prev.map(s => {
if (s.id !== scenarioId) return s
return {
...s,
assumptions: s.assumptions.map(a => a.key === key ? { ...a, value } : a),
}
}))
// Save to DB
await fetch('/api/financial-model/assumptions', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ scenarioId, key, value }),
})
// Debounced recompute
if (computeTimer.current) clearTimeout(computeTimer.current)
computeTimer.current = setTimeout(() => compute(scenarioId), 300)
}, [compute])
const computeAll = useCallback(async () => {
for (const s of scenarios) {
await compute(s.id)
}
}, [scenarios, compute])
const activeScenario = scenarios.find(s => s.id === activeScenarioId) || null
const activeResults = activeScenarioId ? results.get(activeScenarioId) || null : null
return {
scenarios,
activeScenario,
activeScenarioId,
setActiveScenarioId,
activeResults,
results,
loading,
computing,
compareMode,
setCompareMode,
compute,
computeAll,
updateAssumption,
}
}

View File

@@ -0,0 +1,98 @@
'use client'
import { useEffect, useCallback } from 'react'
interface UseKeyboardProps {
onNext: () => void
onPrev: () => void
onFirst: () => void
onLast: () => void
onOverview: () => void
onFullscreen: () => void
onLanguageToggle: () => void
onMenuToggle: () => void
onGoToSlide: (index: number) => void
enabled?: boolean
}
export function useKeyboard({
onNext,
onPrev,
onFirst,
onLast,
onOverview,
onFullscreen,
onLanguageToggle,
onMenuToggle,
onGoToSlide,
enabled = true,
}: UseKeyboardProps) {
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
if (!enabled) return
const target = e.target as HTMLElement
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
return
}
switch (e.key) {
case 'ArrowRight':
case ' ':
case 'Enter':
e.preventDefault()
onNext()
break
case 'ArrowLeft':
e.preventDefault()
onPrev()
break
case 'Escape':
e.preventDefault()
onOverview()
break
case 'f':
case 'F':
e.preventDefault()
onFullscreen()
break
case 'Home':
e.preventDefault()
onFirst()
break
case 'End':
e.preventDefault()
onLast()
break
case 'l':
case 'L':
e.preventDefault()
onLanguageToggle()
break
case 'm':
case 'M':
e.preventDefault()
onMenuToggle()
break
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
e.preventDefault()
onGoToSlide(parseInt(e.key) - 1)
break
}
},
[enabled, onNext, onPrev, onFirst, onLast, onOverview, onFullscreen, onLanguageToggle, onMenuToggle, onGoToSlide]
)
useEffect(() => {
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [handleKeyDown])
}

View File

@@ -0,0 +1,39 @@
'use client'
import { createContext, useContext, useState, useCallback, ReactNode } from 'react'
import { Language } from '../types'
import React from 'react'
interface LanguageContextType {
lang: Language
toggleLanguage: () => void
setLanguage: (lang: Language) => void
}
const LanguageContext = createContext<LanguageContextType>({
lang: 'de',
toggleLanguage: () => {},
setLanguage: () => {},
})
export function LanguageProvider({ children }: { children: ReactNode }) {
const [lang, setLang] = useState<Language>('de')
const toggleLanguage = useCallback(() => {
setLang(prev => prev === 'de' ? 'en' : 'de')
}, [])
const setLanguage = useCallback((newLang: Language) => {
setLang(newLang)
}, [])
return React.createElement(
LanguageContext.Provider,
{ value: { lang, toggleLanguage, setLanguage } },
children
)
}
export function useLanguage() {
return useContext(LanguageContext)
}

View File

@@ -0,0 +1,29 @@
'use client'
import { useState, useEffect } from 'react'
import { PitchData } from '../types'
export function usePitchData() {
const [data, setData] = useState<PitchData | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
async function fetchData() {
try {
const res = await fetch('/api/data')
if (!res.ok) throw new Error(`HTTP ${res.status}`)
const json = await res.json()
setData(json)
} catch (err) {
console.error('Failed to load pitch data:', err)
setError(err instanceof Error ? err.message : 'Unknown error')
} finally {
setLoading(false)
}
}
fetchData()
}, [])
return { data, loading, error }
}

View File

@@ -0,0 +1,77 @@
'use client'
import { useState, useCallback } from 'react'
import { SlideId } from '../types'
const SLIDE_ORDER: SlideId[] = [
'cover',
'problem',
'solution',
'product',
'how-it-works',
'market',
'business-model',
'traction',
'competition',
'team',
'financials',
'the-ask',
'ai-qa',
]
export const TOTAL_SLIDES = SLIDE_ORDER.length
export function useSlideNavigation() {
const [currentIndex, setCurrentIndex] = useState(0)
const [direction, setDirection] = useState(0)
const [visitedSlides, setVisitedSlides] = useState<Set<number>>(new Set([0]))
const [showOverview, setShowOverview] = useState(false)
const currentSlide = SLIDE_ORDER[currentIndex]
const goToSlide = useCallback((index: number) => {
if (index < 0 || index >= TOTAL_SLIDES) return
setDirection(index > currentIndex ? 1 : -1)
setCurrentIndex(index)
setVisitedSlides(prev => new Set([...prev, index]))
setShowOverview(false)
}, [currentIndex])
const nextSlide = useCallback(() => {
if (currentIndex < TOTAL_SLIDES - 1) {
goToSlide(currentIndex + 1)
}
}, [currentIndex, goToSlide])
const prevSlide = useCallback(() => {
if (currentIndex > 0) {
goToSlide(currentIndex - 1)
}
}, [currentIndex, goToSlide])
const goToFirst = useCallback(() => goToSlide(0), [goToSlide])
const goToLast = useCallback(() => goToSlide(TOTAL_SLIDES - 1), [goToSlide])
const toggleOverview = useCallback(() => {
setShowOverview(prev => !prev)
}, [])
return {
currentIndex,
currentSlide,
direction,
visitedSlides,
showOverview,
totalSlides: TOTAL_SLIDES,
slideOrder: SLIDE_ORDER,
goToSlide,
nextSlide,
prevSlide,
goToFirst,
goToLast,
toggleOverview,
setShowOverview,
isFirst: currentIndex === 0,
isLast: currentIndex === TOTAL_SLIDES - 1,
}
}

410
pitch-deck/lib/i18n.ts Normal file
View File

@@ -0,0 +1,410 @@
import { Language } from './types'
const translations = {
de: {
nav: {
slides: 'Slides',
fullscreen: 'Vollbild',
language: 'Sprache',
},
slideNames: [
'Cover',
'Das Problem',
'Die Loesung',
'Produkte',
'So funktioniert\'s',
'Markt',
'Geschaeftsmodell',
'Traction',
'Wettbewerb',
'Team',
'Finanzen',
'The Ask',
'KI Q&A',
],
cover: {
tagline: 'Datensouveraenitaet meets KI-Compliance',
subtitle: 'Pre-Seed · Q4 2026',
cta: 'Pitch starten',
},
problem: {
title: 'Das Problem',
subtitle: 'Compliance-Komplexitaet ueberfordert den Mittelstand',
cards: [
{
title: 'DSGVO',
stat: '4.1 Mrd EUR',
desc: 'Bussgelder seit 2018 in der EU. 83% der KMUs sind nicht vollstaendig konform.',
},
{
title: 'AI Act',
stat: 'Aug 2025',
desc: 'Neue EU-Verordnung tritt in Kraft. Unternehmen muessen KI-Systeme klassifizieren und dokumentieren.',
},
{
title: 'NIS2',
stat: '30.000+',
desc: 'Unternehmen in Deutschland neu betroffen. Cybersecurity-Anforderungen steigen massiv.',
},
],
quote: 'Unternehmen brauchen keine weiteren Compliance-Tools — sie brauchen eine KI, die Compliance fuer sie erledigt.',
},
solution: {
title: 'Die Loesung',
subtitle: 'ComplAI — Compliance auf Autopilot',
pillars: [
{
title: 'Self-Hosted',
desc: 'Eigene Hardware im Serverraum. Kein Byte verlaesst das Unternehmen. Volle Datensouveraenitaet.',
icon: 'server',
},
{
title: 'Auto-Compliance',
desc: 'KI erledigt DSGVO, AI Act und NIS2 automatisch. Dokumentation, Audits und Updates — alles KI-gesteuert.',
icon: 'shield',
},
{
title: 'KI-Assistent',
desc: 'Vollautonomer Kundensupport. Beantwortet Fragen, aendert Dokumente, bereitet Audits vor — 24/7.',
icon: 'bot',
},
],
},
product: {
title: 'Unsere Produkte',
subtitle: 'Drei Tiers fuer jede Unternehmensgroesse',
monthly: '/Monat',
hardware: 'Hardware',
llm: 'KI-Modell',
popular: 'Beliebt',
features: 'Features',
},
howItWorks: {
title: 'So funktioniert\'s',
subtitle: 'In 4 Schritten zur vollstaendigen Compliance',
steps: [
{
title: 'Hardware aufstellen',
desc: 'Mac Mini oder Mac Studio im Serverraum anschliessen. Plug & Play — keine Cloud noetig.',
},
{
title: 'KI konfigurieren',
desc: 'Branche, Groesse und Regularien angeben. Die KI erstellt automatisch alle Compliance-Dokumente.',
},
{
title: 'Compliance automatisieren',
desc: 'Laufende Ueberwachung, automatische Updates bei Rechtsaenderungen und Audit-Vorbereitung.',
},
{
title: 'Audit bestehen',
desc: 'Vollstaendige Dokumentation auf Knopfdruck. Behoerdenanfragen werden KI-gestuetzt beantwortet.',
},
],
},
market: {
title: 'Marktchance',
subtitle: 'Der Compliance-Markt waechst zweistellig',
tam: 'TAM',
sam: 'SAM',
som: 'SOM',
tamLabel: 'Total Addressable Market',
samLabel: 'Serviceable Addressable Market',
somLabel: 'Serviceable Obtainable Market',
source: 'Quelle',
growth: 'Wachstum p.a.',
},
businessModel: {
title: 'Geschaeftsmodell',
subtitle: 'Recurring Revenue mit Hardware-Moat',
unitEconomics: 'Unit Economics',
amortization: 'Amortisation',
margin: 'Marge',
months: 'Monate',
recurringRevenue: 'Recurring Revenue',
hardwareCost: 'Hardware-EK',
operatingCost: 'Betriebskosten',
},
traction: {
title: 'Traction & Meilensteine',
subtitle: 'Unser bisheriger Fortschritt',
completed: 'Abgeschlossen',
inProgress: 'In Arbeit',
planned: 'Geplant',
},
competition: {
title: 'Wettbewerb',
subtitle: 'Was uns differenziert',
feature: 'Feature',
selfHosted: 'Self-Hosted',
integratedAI: 'Integrierte KI',
autonomousSupport: 'Autonomer Support',
yes: 'Ja',
no: 'Nein',
partial: 'Teilweise',
},
team: {
title: 'Das Team',
subtitle: 'Gruender mit Domain-Expertise',
equity: 'Equity',
expertise: 'Expertise',
},
financials: {
title: 'Finanzprognose',
subtitle: 'AI-First Kostenstruktur — skaliert ohne lineares Personalwachstum',
revenue: 'Umsatz',
costs: 'Kosten',
customers: 'Kunden',
mrr: 'MRR',
arr: 'ARR',
burnRate: 'Burn Rate',
employees: 'Mitarbeiter',
year: 'Jahr',
sliderGrowth: 'Wachstumsrate',
sliderChurn: 'Churn Rate',
sliderArpu: 'ARPU',
adjustAssumptions: 'Annahmen anpassen',
},
theAsk: {
title: 'The Ask',
subtitle: 'Pre-Seed Finanzierung',
amount: 'Funding',
instrument: 'Instrument',
useOfFunds: 'Use of Funds',
engineering: 'Engineering',
sales: 'Vertrieb',
hardware: 'Hardware',
legal: 'Legal',
reserve: 'Reserve',
targetDate: 'Zieldatum',
},
aiqa: {
title: 'Fragen? Die KI antwortet.',
subtitle: 'Stellen Sie Ihre Investorenfragen — unser AI Agent antwortet mit Echtdaten.',
placeholder: 'Stellen Sie eine Frage zum Investment...',
send: 'Senden',
thinking: 'Denke nach...',
suggestions: [
'Wie skaliert das Geschaeftsmodell?',
'Was ist der unfaire Vorteil?',
'Wie sieht die Exit-Strategie aus?',
'Warum Self-Hosting statt Cloud?',
],
},
},
en: {
nav: {
slides: 'Slides',
fullscreen: 'Fullscreen',
language: 'Language',
},
slideNames: [
'Cover',
'The Problem',
'The Solution',
'Products',
'How It Works',
'Market',
'Business Model',
'Traction',
'Competition',
'Team',
'Financials',
'The Ask',
'AI Q&A',
],
cover: {
tagline: 'Data Sovereignty meets AI Compliance',
subtitle: 'Pre-Seed · Q4 2026',
cta: 'Start Pitch',
},
problem: {
title: 'The Problem',
subtitle: 'Compliance complexity overwhelms SMEs',
cards: [
{
title: 'GDPR',
stat: 'EUR 4.1B',
desc: 'in fines since 2018 across the EU. 83% of SMEs are not fully compliant.',
},
{
title: 'AI Act',
stat: 'Aug 2025',
desc: 'New EU regulation takes effect. Companies must classify and document AI systems.',
},
{
title: 'NIS2',
stat: '30,000+',
desc: 'companies newly affected in Germany. Cybersecurity requirements increase massively.',
},
],
quote: 'Companies don\'t need more compliance tools — they need an AI that handles compliance for them.',
},
solution: {
title: 'The Solution',
subtitle: 'ComplAI — Compliance on Autopilot',
pillars: [
{
title: 'Self-Hosted',
desc: 'Own hardware in your server room. No data leaves the company. Full data sovereignty.',
icon: 'server',
},
{
title: 'Auto-Compliance',
desc: 'AI handles GDPR, AI Act and NIS2 automatically. Documentation, audits and updates — all AI-powered.',
icon: 'shield',
},
{
title: 'AI Assistant',
desc: 'Fully autonomous customer support. Answers questions, modifies documents, prepares audits — 24/7.',
icon: 'bot',
},
],
},
product: {
title: 'Our Products',
subtitle: 'Three tiers for every company size',
monthly: '/month',
hardware: 'Hardware',
llm: 'AI Model',
popular: 'Popular',
features: 'Features',
},
howItWorks: {
title: 'How It Works',
subtitle: 'Full compliance in 4 steps',
steps: [
{
title: 'Set Up Hardware',
desc: 'Connect Mac Mini or Mac Studio in your server room. Plug & Play — no cloud needed.',
},
{
title: 'Configure AI',
desc: 'Specify industry, size, and regulations. The AI automatically creates all compliance documents.',
},
{
title: 'Automate Compliance',
desc: 'Continuous monitoring, automatic updates for regulatory changes and audit preparation.',
},
{
title: 'Pass Audits',
desc: 'Complete documentation at the push of a button. Authority inquiries answered AI-powered.',
},
],
},
market: {
title: 'Market Opportunity',
subtitle: 'The compliance market grows double-digit',
tam: 'TAM',
sam: 'SAM',
som: 'SOM',
tamLabel: 'Total Addressable Market',
samLabel: 'Serviceable Addressable Market',
somLabel: 'Serviceable Obtainable Market',
source: 'Source',
growth: 'Growth p.a.',
},
businessModel: {
title: 'Business Model',
subtitle: 'Recurring Revenue with Hardware Moat',
unitEconomics: 'Unit Economics',
amortization: 'Amortization',
margin: 'Margin',
months: 'months',
recurringRevenue: 'Recurring Revenue',
hardwareCost: 'Hardware Cost',
operatingCost: 'Operating Cost',
},
traction: {
title: 'Traction & Milestones',
subtitle: 'Our progress so far',
completed: 'Completed',
inProgress: 'In Progress',
planned: 'Planned',
},
competition: {
title: 'Competition',
subtitle: 'What differentiates us',
feature: 'Feature',
selfHosted: 'Self-Hosted',
integratedAI: 'Integrated AI',
autonomousSupport: 'Autonomous Support',
yes: 'Yes',
no: 'No',
partial: 'Partial',
},
team: {
title: 'The Team',
subtitle: 'Founders with domain expertise',
equity: 'Equity',
expertise: 'Expertise',
},
financials: {
title: 'Financial Projections',
subtitle: 'AI-First cost structure — scales without linear headcount growth',
revenue: 'Revenue',
costs: 'Costs',
customers: 'Customers',
mrr: 'MRR',
arr: 'ARR',
burnRate: 'Burn Rate',
employees: 'Employees',
year: 'Year',
sliderGrowth: 'Growth Rate',
sliderChurn: 'Churn Rate',
sliderArpu: 'ARPU',
adjustAssumptions: 'Adjust Assumptions',
},
theAsk: {
title: 'The Ask',
subtitle: 'Pre-Seed Funding',
amount: 'Funding',
instrument: 'Instrument',
useOfFunds: 'Use of Funds',
engineering: 'Engineering',
sales: 'Sales',
hardware: 'Hardware',
legal: 'Legal',
reserve: 'Reserve',
targetDate: 'Target Date',
},
aiqa: {
title: 'Questions? The AI answers.',
subtitle: 'Ask your investor questions — our AI agent responds with real data.',
placeholder: 'Ask a question about the investment...',
send: 'Send',
thinking: 'Thinking...',
suggestions: [
'How does the business model scale?',
'What is the unfair advantage?',
'What does the exit strategy look like?',
'Why self-hosting instead of cloud?',
],
},
},
}
export function t(lang: Language): typeof translations.de {
return translations[lang]
}
export function formatEur(value: number, lang: Language): string {
if (value >= 1_000_000_000) {
const v = (value / 1_000_000_000).toFixed(1)
return lang === 'de' ? `${v} Mrd. EUR` : `EUR ${v}B`
}
if (value >= 1_000_000) {
const v = (value / 1_000_000).toFixed(1)
return lang === 'de' ? `${v} Mio. EUR` : `EUR ${v}M`
}
if (value >= 1_000) {
const v = (value / 1_000).toFixed(0)
return lang === 'de' ? `${v}k EUR` : `EUR ${v}k`
}
return lang === 'de' ? `${value} EUR` : `EUR ${value}`
}
export function formatNumber(value: number): string {
return new Intl.NumberFormat('de-DE').format(value)
}
export default translations

216
pitch-deck/lib/types.ts Normal file
View File

@@ -0,0 +1,216 @@
export interface PitchCompany {
id: number
name: string
legal_form: string
founding_date: string
tagline_de: string
tagline_en: string
mission_de: string
mission_en: string
website: string
hq_city: string
}
export interface PitchTeamMember {
id: number
name: string
role_de: string
role_en: string
bio_de: string
bio_en: string
equity_pct: number
expertise: string[]
linkedin_url: string
photo_url: string
}
export interface PitchFinancial {
id: number
year: number
revenue_eur: number
costs_eur: number
mrr_eur: number
burn_rate_eur: number
customers_count: number
employees_count: number
arr_eur: number
}
export interface PitchMarket {
id: number
market_segment: string
label: string
value_eur: number
growth_rate_pct: number
source: string
}
export interface PitchCompetitor {
id: number
name: string
customers_count: number
pricing_range: string
strengths: string[]
weaknesses: string[]
website: string
}
export interface PitchFeature {
id: number
feature_name_de: string
feature_name_en: string
category: string
breakpilot: boolean
proliance: boolean
dataguard: boolean
heydata: boolean
is_differentiator: boolean
}
export interface PitchMilestone {
id: number
milestone_date: string
title_de: string
title_en: string
description_de: string
description_en: string
status: 'completed' | 'in_progress' | 'planned'
category: string
}
export interface PitchMetric {
id: number
metric_name: string
label_de: string
label_en: string
value: string
unit: string
is_live: boolean
}
export interface PitchFunding {
id: number
round_name: string
amount_eur: number
use_of_funds: { category: string; percentage: number; label_de: string; label_en: string }[]
instrument: string
target_date: string
status: string
}
export interface PitchProduct {
id: number
name: string
hardware: string
hardware_cost_eur: number
monthly_price_eur: number
llm_model: string
llm_size: string
llm_capability_de: string
llm_capability_en: string
features_de: string[]
features_en: string[]
is_popular: boolean
operating_cost_eur: number
}
export interface PitchData {
company: PitchCompany
team: PitchTeamMember[]
financials: PitchFinancial[]
market: PitchMarket[]
competitors: PitchCompetitor[]
features: PitchFeature[]
milestones: PitchMilestone[]
metrics: PitchMetric[]
funding: PitchFunding
products: PitchProduct[]
}
// Financial Model Types
export interface FMScenario {
id: string
name: string
description: string
is_default: boolean
color: string
assumptions: FMAssumption[]
}
export interface FMAssumption {
id: string
scenario_id: string
key: string
label_de: string
label_en: string
value: number | number[]
value_type: 'scalar' | 'step' | 'timeseries'
unit: string
min_value: number | null
max_value: number | null
step_size: number | null
category: string
sort_order: number
}
export interface FMResult {
month: number
year: number
month_in_year: number
new_customers: number
churned_customers: number
total_customers: number
mrr_eur: number
arr_eur: number
revenue_eur: number
cogs_eur: number
personnel_eur: number
infra_eur: number
marketing_eur: number
total_costs_eur: number
employees_count: number
gross_margin_pct: number
burn_rate_eur: number
runway_months: number
cac_eur: number
ltv_eur: number
ltv_cac_ratio: number
cash_balance_eur: number
cumulative_revenue_eur: number
}
export interface FMComputeResponse {
scenario_id: string
results: FMResult[]
summary: {
final_arr: number
final_customers: number
break_even_month: number | null
final_runway: number
final_ltv_cac: number
peak_burn: number
total_funding_needed: number
}
}
export type Language = 'de' | 'en'
export interface ChatMessage {
role: 'user' | 'assistant'
content: string
}
export type SlideId =
| 'cover'
| 'problem'
| 'solution'
| 'product'
| 'how-it-works'
| 'market'
| 'business-model'
| 'traction'
| 'competition'
| 'team'
| 'financials'
| 'the-ask'
| 'ai-qa'