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,40 @@
'use client'
import { Language } from '@/lib/types'
import { t } from '@/lib/i18n'
import { Bot } from 'lucide-react'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import ChatInterface from '../ui/ChatInterface'
import LiveIndicator from '../ui/LiveIndicator'
interface AIQASlideProps {
lang: Language
}
export default function AIQASlide({ lang }: AIQASlideProps) {
const i = t(lang)
return (
<div>
<FadeInView className="text-center mb-8">
<div className="flex items-center justify-center gap-3 mb-4">
<div className="w-12 h-12 rounded-xl bg-indigo-500/20 flex items-center justify-center">
<Bot className="w-6 h-6 text-indigo-400" />
</div>
<LiveIndicator />
</div>
<h2 className="text-4xl md:text-5xl font-bold mb-3">
<GradientText>{i.aiqa.title}</GradientText>
</h2>
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.aiqa.subtitle}</p>
</FadeInView>
<FadeInView delay={0.3}>
<div className="max-w-3xl mx-auto bg-white/[0.04] border border-white/10 rounded-2xl p-6">
<ChatInterface lang={lang} />
</div>
</FadeInView>
</div>
)
}

View File

@@ -0,0 +1,99 @@
'use client'
import { motion } from 'framer-motion'
import { Language, PitchProduct } from '@/lib/types'
import { t } from '@/lib/i18n'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import GlassCard from '../ui/GlassCard'
import { DollarSign, Repeat, TrendingUp } from 'lucide-react'
interface BusinessModelSlideProps {
lang: Language
products: PitchProduct[]
}
export default function BusinessModelSlide({ lang, products }: BusinessModelSlideProps) {
const i = t(lang)
return (
<div>
<FadeInView className="text-center mb-12">
<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">{i.businessModel.subtitle}</p>
</FadeInView>
{/* Key Metrics */}
<div className="grid md:grid-cols-3 gap-4 mb-8">
<GlassCard delay={0.2} className="text-center">
<Repeat className="w-6 h-6 text-indigo-400 mx-auto mb-2" />
<p className="text-sm text-white/50 mb-1">{i.businessModel.recurringRevenue}</p>
<p className="text-2xl font-bold text-white">100%</p>
<p className="text-xs text-white/30">SaaS / Subscription</p>
</GlassCard>
<GlassCard delay={0.3} className="text-center">
<DollarSign className="w-6 h-6 text-green-400 mx-auto mb-2" />
<p className="text-sm text-white/50 mb-1">{i.businessModel.margin}</p>
<p className="text-2xl font-bold text-white">&gt;70%</p>
<p className="text-xs text-white/30">{lang === 'de' ? 'nach Amortisation' : 'post amortization'}</p>
</GlassCard>
<GlassCard delay={0.4} className="text-center">
<TrendingUp className="w-6 h-6 text-purple-400 mx-auto mb-2" />
<p className="text-sm text-white/50 mb-1">{i.businessModel.amortization}</p>
<p className="text-2xl font-bold text-white">24 {i.businessModel.months}</p>
<p className="text-xs text-white/30">{lang === 'de' ? 'Hardware-Amortisation' : 'Hardware Amortization'}</p>
</GlassCard>
</div>
{/* Unit Economics per Product */}
<FadeInView delay={0.5}>
<h3 className="text-lg font-semibold mb-4 text-white/70">{i.businessModel.unitEconomics}</h3>
<div className="grid md:grid-cols-3 gap-4">
{products.map((p, idx) => {
const amort = p.hardware_cost_eur > 0 ? Math.round(p.hardware_cost_eur / 24) : 0
const monthlyMargin = p.monthly_price_eur - amort - (p.operating_cost_eur > 0 ? p.operating_cost_eur : 0)
const marginPct = Math.round((monthlyMargin / p.monthly_price_eur) * 100)
return (
<motion.div
key={p.id}
initial={{ opacity: 0, rotateY: -15 }}
animate={{ opacity: 1, rotateY: 0 }}
transition={{ delay: 0.6 + idx * 0.15 }}
className="bg-white/[0.05] border border-white/10 rounded-2xl p-5"
>
<h4 className="font-bold text-white mb-3">{p.name}</h4>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-white/50">{lang === 'de' ? 'Monatspreis' : 'Monthly Price'}</span>
<span className="text-white font-medium">{p.monthly_price_eur} EUR</span>
</div>
{p.hardware_cost_eur > 0 && (
<div className="flex justify-between">
<span className="text-white/50">{i.businessModel.hardwareCost}</span>
<span className="text-white/70">-{amort} EUR/Mo</span>
</div>
)}
{p.operating_cost_eur > 0 && (
<div className="flex justify-between">
<span className="text-white/50">{i.businessModel.operatingCost}</span>
<span className="text-white/70">-{p.operating_cost_eur.toLocaleString('de-DE')} EUR/Mo</span>
</div>
)}
<div className="border-t border-white/10 pt-2 flex justify-between">
<span className="text-white/50">{i.businessModel.margin}</span>
<span className={`font-bold ${marginPct > 0 ? 'text-green-400' : 'text-red-400'}`}>
{marginPct > 0 ? '+' : ''}{monthlyMargin} EUR ({marginPct}%)
</span>
</div>
</div>
</motion.div>
)
})}
</div>
</FadeInView>
</div>
)
}

View File

@@ -0,0 +1,107 @@
'use client'
import { Language, PitchFeature, PitchCompetitor } from '@/lib/types'
import { t } from '@/lib/i18n'
import { ShieldCheck, Code2, ScanLine, FileSearch, Package, Bug } from 'lucide-react'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import FeatureMatrix from '../ui/FeatureMatrix'
import GlassCard from '../ui/GlassCard'
import BrandName from '../ui/BrandName'
interface CompetitionSlideProps {
lang: Language
features: PitchFeature[]
competitors: PitchCompetitor[]
}
const securityFeatures = {
de: [
{ icon: ShieldCheck, title: 'DevSecOps Security Suite', desc: '6 integrierte Security-Tools fuer kontinuierliche Sicherheitsueberwachung' },
{ icon: ScanLine, title: 'SAST & Secrets Detection', desc: 'Automatische Code-Analyse (Semgrep) + Secrets-Scanning (Gitleaks) in der CI/CD Pipeline' },
{ icon: Bug, title: 'Container & Dependency Scanning', desc: 'Trivy + Grype scannen Container-Images und Abhaengigkeiten auf CVEs' },
{ icon: Package, title: 'SBOM-Generator (NIS2-konform)', desc: 'CycloneDX/SPDX Software Bill of Materials fuer NIS2 und ISO 27001 Compliance' },
{ icon: FileSearch, title: 'Software-Risikoanalyse', desc: 'Automatisierte Risikoklassifizierung fuer Embedded-Entwicklung und AI-Act-konforme Systeme' },
{ icon: Code2, title: 'KI-Code-Assistent (1000b)', desc: 'Das Cloud-LLM unterstuetzt Entwickler bei Code-Reviews, Security-Fixes und Compliance-Dokumentation' },
],
en: [
{ icon: ShieldCheck, title: 'DevSecOps Security Suite', desc: '6 integrated security tools for continuous security monitoring' },
{ icon: ScanLine, title: 'SAST & Secrets Detection', desc: 'Automatic code analysis (Semgrep) + secrets scanning (Gitleaks) in CI/CD pipeline' },
{ icon: Bug, title: 'Container & Dependency Scanning', desc: 'Trivy + Grype scan container images and dependencies for CVEs' },
{ icon: Package, title: 'SBOM Generator (NIS2 compliant)', desc: 'CycloneDX/SPDX Software Bill of Materials for NIS2 and ISO 27001 compliance' },
{ icon: FileSearch, title: 'Software Risk Analysis', desc: 'Automated risk classification for embedded development and AI Act compliant systems' },
{ icon: Code2, title: 'AI Code Assistant (1000b)', desc: 'Cloud LLM assists developers with code reviews, security fixes and compliance documentation' },
],
}
export default function CompetitionSlide({ lang, features, competitors }: CompetitionSlideProps) {
const i = t(lang)
const coreFeatures = features.filter(f => f.category !== 'security')
const secFeats = securityFeatures[lang]
return (
<div>
<FadeInView className="text-center mb-8">
<h2 className="text-4xl md:text-5xl font-bold mb-3">
<GradientText>{i.competition.title}</GradientText>
</h2>
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.competition.subtitle}</p>
</FadeInView>
{/* Feature Matrix (Core Compliance) */}
<FadeInView delay={0.3}>
<GlassCard className="mb-6 p-4 overflow-x-auto" hover={false}>
<FeatureMatrix features={coreFeatures} lang={lang} />
</GlassCard>
</FadeInView>
{/* Security & Developer Features — nur bei ComplAI */}
<FadeInView delay={0.5}>
<div className="mb-6">
<h3 className="text-sm font-semibold text-indigo-400 mb-3 flex items-center gap-2">
<ShieldCheck className="w-4 h-4" />
{lang === 'de' ? <>Integrierte Security &amp; Developer Tools nur bei <BrandName /></> : <>Integrated Security &amp; Developer Tools <BrandName /> only</>}
</h3>
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
{secFeats.map((feat, idx) => {
const Icon = feat.icon
return (
<FadeInView key={idx} delay={0.6 + idx * 0.08}>
<div className="bg-indigo-500/5 border border-indigo-500/10 rounded-xl p-3">
<div className="flex items-center gap-2 mb-1.5">
<Icon className="w-4 h-4 text-indigo-400 shrink-0" />
<span className="text-xs font-semibold text-white">{feat.title}</span>
</div>
<p className="text-[11px] text-white/40 leading-relaxed">{feat.desc}</p>
</div>
</FadeInView>
)
})}
</div>
</div>
</FadeInView>
{/* Competitor Summary */}
<div className="grid md:grid-cols-3 gap-4">
{competitors.map((c, idx) => (
<FadeInView key={c.id} delay={0.9 + idx * 0.1}>
<div className="bg-white/[0.04] border border-white/5 rounded-xl p-4">
<div className="flex items-center justify-between mb-2">
<h4 className="font-medium text-white/70">{c.name}</h4>
<span className="text-xs text-white/30">{c.customers_count.toLocaleString()} {lang === 'de' ? 'Kunden' : 'customers'}</span>
</div>
<p className="text-xs text-white/40 mb-2">{c.pricing_range}</p>
<div className="flex flex-wrap gap-1">
{(c.weaknesses || []).slice(0, 2).map((w, widx) => (
<span key={widx} className="text-xs px-2 py-0.5 rounded-full bg-red-500/10 text-red-400">
{w}
</span>
))}
</div>
</div>
</FadeInView>
))}
</div>
</div>
)
}

View File

@@ -0,0 +1,93 @@
'use client'
import { motion } from 'framer-motion'
import { Language } from '@/lib/types'
import { t } from '@/lib/i18n'
import { ArrowRight } from 'lucide-react'
import GradientText from '../ui/GradientText'
import BrandName from '../ui/BrandName'
interface CoverSlideProps {
lang: Language
onNext: () => void
}
export default function CoverSlide({ lang, onNext }: CoverSlideProps) {
const i = t(lang)
return (
<div className="flex flex-col items-center justify-center text-center min-h-[70vh]">
{/* Logo / Brand */}
<motion.div
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.8, ease: [0.22, 1, 0.36, 1] }}
className="mb-8"
>
<div className="w-20 h-20 mx-auto mb-6 rounded-2xl bg-gradient-to-br from-indigo-500 to-purple-600
flex items-center justify-center shadow-lg shadow-indigo-500/30">
<svg width="40" height="40" viewBox="0 0 40 40" fill="none">
<path d="M8 12L20 6L32 12V28L20 34L8 28V12Z" stroke="white" strokeWidth="2" fill="none" />
<path d="M20 6V34" stroke="white" strokeWidth="1.5" opacity="0.5" />
<path d="M8 12L32 28" stroke="white" strokeWidth="1.5" opacity="0.3" />
<path d="M32 12L8 28" stroke="white" strokeWidth="1.5" opacity="0.3" />
<circle cx="20" cy="20" r="4" fill="white" opacity="0.8" />
</svg>
</div>
</motion.div>
{/* Company Name */}
<motion.h1
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 }}
className="text-5xl md:text-7xl font-bold mb-4 tracking-tight"
>
BreakPilot{' '}
<motion.span
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.5 }}
>
<BrandName className="text-5xl md:text-7xl font-bold" />
</motion.span>
</motion.h1>
{/* Tagline */}
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.8, delay: 0.6 }}
className="text-xl md:text-2xl text-white/60 mb-2 max-w-2xl"
>
{i.cover.tagline}
</motion.p>
{/* Subtitle */}
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5, delay: 0.8 }}
className="text-sm text-white/30 font-mono tracking-wider mb-12"
>
{i.cover.subtitle}
</motion.p>
{/* CTA */}
<motion.button
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 1.2 }}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={onNext}
className="group flex items-center gap-2 px-8 py-3 rounded-full
bg-indigo-500 hover:bg-indigo-600 transition-colors text-white font-medium
shadow-lg shadow-indigo-500/30"
>
{i.cover.cta}
<ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
</motion.button>
</div>
)
}

View File

@@ -0,0 +1,277 @@
'use client'
import { useState } from 'react'
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 FinancialChart from '../ui/FinancialChart'
import FinancialSliders from '../ui/FinancialSliders'
import KPICard from '../ui/KPICard'
import RunwayGauge from '../ui/RunwayGauge'
import WaterfallChart from '../ui/WaterfallChart'
import UnitEconomicsCards from '../ui/UnitEconomicsCards'
import ScenarioSwitcher from '../ui/ScenarioSwitcher'
import AnnualPLTable from '../ui/AnnualPLTable'
import AnnualCashflowChart from '../ui/AnnualCashflowChart'
type FinTab = 'overview' | 'guv' | 'cashflow'
interface FinancialsSlideProps {
lang: Language
}
export default function FinancialsSlide({ lang }: FinancialsSlideProps) {
const i = t(lang)
const fm = useFinancialModel()
const [activeTab, setActiveTab] = useState<FinTab>('overview')
const de = lang === 'de'
const activeResults = fm.activeResults
const summary = activeResults?.summary
const lastResult = activeResults?.results[activeResults.results.length - 1]
// Build scenario color map
const scenarioColors: Record<string, string> = {}
fm.scenarios.forEach(s => { scenarioColors[s.id] = s.color })
// Build compare results (exclude active scenario)
const compareResults = new Map(
Array.from(fm.results.entries()).filter(([id]) => id !== fm.activeScenarioId)
)
// Initial funding from assumptions
const initialFunding = (fm.activeScenario?.assumptions.find(a => a.key === 'initial_funding')?.value as number) || 200000
const tabs: { id: FinTab; label: string }[] = [
{ id: 'overview', label: de ? 'Uebersicht' : 'Overview' },
{ id: 'guv', label: de ? 'GuV (Jahres)' : 'P&L (Annual)' },
{ id: 'cashflow', label: de ? 'Cashflow & Finanzbedarf' : 'Cashflow & Funding' },
]
if (fm.loading) {
return (
<div className="flex items-center justify-center h-full">
<div className="w-8 h-8 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" />
</div>
)
}
return (
<div className="max-w-6xl mx-auto px-4">
<FadeInView className="text-center mb-3">
<h2 className="text-3xl md:text-4xl font-bold mb-1">
<GradientText>{i.financials.title}</GradientText>
</h2>
<p className="text-sm text-white/50 max-w-2xl mx-auto">{i.financials.subtitle}</p>
</FadeInView>
{/* Hero KPI Cards */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 mb-3">
<KPICard
label={`ARR 2030`}
value={summary ? Math.round(summary.final_arr / 1_000_000 * 10) / 10 : 0}
suffix=" Mio."
decimals={1}
trend="up"
color="#6366f1"
delay={0.1}
subLabel="EUR"
/>
<KPICard
label={de ? 'Kunden 2030' : 'Customers 2030'}
value={summary?.final_customers || 0}
trend="up"
color="#22c55e"
delay={0.15}
/>
<KPICard
label="Break-Even"
value={summary?.break_even_month || 0}
suffix={de ? ' Mo.' : ' mo.'}
trend={summary?.break_even_month && summary.break_even_month <= 24 ? 'up' : 'down'}
color="#eab308"
delay={0.2}
subLabel={summary?.break_even_month ? `~${Math.ceil((summary.break_even_month) / 12) + 2025}` : ''}
/>
<KPICard
label="LTV/CAC"
value={summary?.final_ltv_cac || 0}
suffix="x"
decimals={1}
trend={(summary?.final_ltv_cac || 0) >= 3 ? 'up' : 'down'}
color="#a855f7"
delay={0.25}
/>
</div>
{/* Tab Navigation */}
<div className="flex items-center gap-1 mb-3">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`px-3 py-1.5 rounded-lg text-xs transition-all
${activeTab === tab.id
? 'bg-indigo-500/20 text-indigo-300 border border-indigo-500/30'
: 'bg-white/[0.04] text-white/40 border border-transparent hover:text-white/60 hover:bg-white/[0.06]'
}`}
>
{tab.label}
</button>
))}
</div>
{/* Main content: 3-column layout */}
<div className="grid md:grid-cols-12 gap-3">
{/* Left: Charts (8 columns) */}
<div className="md:col-span-8 space-y-3">
{/* TAB: Overview — monatlicher Chart + Waterfall + Unit Economics */}
{activeTab === 'overview' && (
<>
<FadeInView delay={0.1}>
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-3">
<div className="flex items-center justify-between mb-2">
<p className="text-xs text-white/40">
{de ? 'Umsatz vs. Kosten (60 Monate)' : 'Revenue vs. Costs (60 months)'}
</p>
<div className="flex items-center gap-3 text-[9px]">
<span className="flex items-center gap-1"><span className="w-2 h-0.5 bg-indigo-500 inline-block" /> {de ? 'Umsatz' : 'Revenue'}</span>
<span className="flex items-center gap-1"><span className="w-2 h-0.5 bg-red-400 inline-block" style={{ borderBottom: '1px dashed' }} /> {de ? 'Kosten' : 'Costs'}</span>
<span className="flex items-center gap-1"><span className="w-2 h-0.5 bg-emerald-500 inline-block" /> {de ? 'Kunden' : 'Customers'}</span>
</div>
</div>
<FinancialChart
activeResults={activeResults}
compareResults={compareResults}
compareMode={fm.compareMode}
scenarioColors={scenarioColors}
lang={lang}
/>
</div>
</FadeInView>
<div className="grid md:grid-cols-2 gap-3">
<FadeInView delay={0.2}>
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-3">
<p className="text-xs text-white/40 mb-2">
{de ? 'Cash-Flow (Quartal)' : 'Cash Flow (Quarterly)'}
</p>
{activeResults && <WaterfallChart results={activeResults.results} lang={lang} />}
</div>
</FadeInView>
<FadeInView delay={0.25}>
<div className="space-y-3">
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-3 flex justify-center">
<RunwayGauge
months={lastResult?.runway_months || 0}
size={120}
label={de ? 'Runway (Monate)' : 'Runway (months)'}
/>
</div>
{lastResult && (
<UnitEconomicsCards
cac={lastResult.cac_eur}
ltv={lastResult.ltv_eur}
ltvCacRatio={lastResult.ltv_cac_ratio}
grossMargin={lastResult.gross_margin_pct}
churnRate={fm.activeScenario?.assumptions.find(a => a.key === 'churn_rate_monthly')?.value as number || 3}
lang={lang}
/>
)}
</div>
</FadeInView>
</div>
</>
)}
{/* TAB: GuV — Annual P&L Table */}
{activeTab === 'guv' && activeResults && (
<FadeInView delay={0.1}>
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-4">
<div className="flex items-center justify-between mb-3">
<p className="text-xs text-white/40">
{de ? 'Gewinn- und Verlustrechnung (5 Jahre)' : 'Profit & Loss Statement (5 Years)'}
</p>
<p className="text-[9px] text-white/20">
{de ? 'Alle Werte in EUR' : 'All values in EUR'}
</p>
</div>
<AnnualPLTable results={activeResults.results} lang={lang} />
</div>
</FadeInView>
)}
{/* TAB: Cashflow & Finanzbedarf */}
{activeTab === 'cashflow' && activeResults && (
<FadeInView delay={0.1}>
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-4">
<p className="text-xs text-white/40 mb-3">
{de ? 'Jaehrlicher Cashflow & Finanzbedarf' : 'Annual Cash Flow & Funding Requirements'}
</p>
<AnnualCashflowChart
results={activeResults.results}
initialFunding={initialFunding}
lang={lang}
/>
</div>
</FadeInView>
)}
</div>
{/* Right: Controls (4 columns) */}
<div className="md:col-span-4 space-y-3">
{/* Scenario Switcher */}
<FadeInView delay={0.15}>
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-3">
<ScenarioSwitcher
scenarios={fm.scenarios}
activeId={fm.activeScenarioId}
compareMode={fm.compareMode}
onSelect={(id) => {
fm.setActiveScenarioId(id)
}}
onToggleCompare={() => {
if (!fm.compareMode) {
fm.computeAll()
}
fm.setCompareMode(!fm.compareMode)
}}
lang={lang}
/>
</div>
</FadeInView>
{/* Assumption Sliders */}
<FadeInView delay={0.2}>
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-3">
<p className="text-[10px] text-white/40 uppercase tracking-wider mb-2">
{i.financials.adjustAssumptions}
</p>
{fm.activeScenario && (
<FinancialSliders
assumptions={fm.activeScenario.assumptions}
onAssumptionChange={(key, value) => {
if (fm.activeScenarioId) {
fm.updateAssumption(fm.activeScenarioId, key, value)
}
}}
lang={lang}
/>
)}
{fm.computing && (
<div className="flex items-center gap-2 mt-2 text-[10px] text-indigo-400">
<div className="w-3 h-3 border border-indigo-400 border-t-transparent rounded-full animate-spin" />
{de ? 'Berechne...' : 'Computing...'}
</div>
)}
</div>
</FadeInView>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,65 @@
'use client'
import { motion } from 'framer-motion'
import { Language } from '@/lib/types'
import { t } from '@/lib/i18n'
import { Plug, Settings, RefreshCw, CheckCircle2 } from 'lucide-react'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
interface HowItWorksSlideProps {
lang: Language
}
const stepIcons = [Plug, Settings, RefreshCw, CheckCircle2]
const stepColors = ['text-blue-400', 'text-indigo-400', 'text-purple-400', 'text-green-400']
export default function HowItWorksSlide({ lang }: HowItWorksSlideProps) {
const i = t(lang)
return (
<div>
<FadeInView className="text-center mb-12">
<h2 className="text-4xl md:text-5xl font-bold mb-3">
<GradientText>{i.howItWorks.title}</GradientText>
</h2>
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.howItWorks.subtitle}</p>
</FadeInView>
<div className="relative max-w-4xl mx-auto">
{/* Connection Line */}
<div className="absolute left-8 top-12 bottom-12 w-px bg-gradient-to-b from-blue-500 via-purple-500 to-green-500 hidden md:block" />
<div className="space-y-8">
{i.howItWorks.steps.map((step, idx) => {
const Icon = stepIcons[idx]
return (
<motion.div
key={idx}
initial={{ opacity: 0, x: -30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.3 + idx * 0.2, duration: 0.5 }}
className="flex items-start gap-6 relative"
>
<div className={`
w-16 h-16 rounded-2xl bg-white/[0.06] border border-white/10
flex items-center justify-center shrink-0 relative z-10
${stepColors[idx]}
`}>
<Icon className="w-7 h-7" />
</div>
<div className="pt-2">
<div className="flex items-center gap-3 mb-1">
<span className="text-xs font-mono text-white/30">0{idx + 1}</span>
<h3 className="text-xl font-bold text-white">{step.title}</h3>
</div>
<p className="text-sm text-white/50 leading-relaxed max-w-lg">{step.desc}</p>
</div>
</motion.div>
)
})}
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,91 @@
'use client'
import { motion } from 'framer-motion'
import { Language, PitchMarket } from '@/lib/types'
import { t, formatEur } from '@/lib/i18n'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import AnimatedCounter from '../ui/AnimatedCounter'
interface MarketSlideProps {
lang: Language
market: PitchMarket[]
}
const sizes = [280, 200, 130]
const colors = ['border-indigo-500/30 bg-indigo-500/5', 'border-purple-500/30 bg-purple-500/5', 'border-blue-500/30 bg-blue-500/5']
const textColors = ['text-indigo-400', 'text-purple-400', 'text-blue-400']
export default function MarketSlide({ lang, market }: MarketSlideProps) {
const i = t(lang)
const labels = [i.market.tamLabel, i.market.samLabel, i.market.somLabel]
const segments = [i.market.tam, i.market.sam, i.market.som]
return (
<div>
<FadeInView className="text-center mb-12">
<h2 className="text-4xl md:text-5xl font-bold mb-3">
<GradientText>{i.market.title}</GradientText>
</h2>
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.market.subtitle}</p>
</FadeInView>
<div className="flex flex-col md:flex-row items-center justify-center gap-12">
{/* Circles */}
<div className="relative flex items-center justify-center" style={{ width: 300, height: 300 }}>
{market.map((m, idx) => (
<motion.div
key={m.id}
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ delay: 0.3 + idx * 0.2, type: 'spring', stiffness: 200 }}
className={`absolute rounded-full border-2 ${colors[idx]} flex items-center justify-center`}
style={{
width: sizes[idx],
height: sizes[idx],
}}
>
{idx === market.length - 1 && (
<div className="text-center">
<span className={`text-xs font-mono ${textColors[idx]}`}>{segments[idx]}</span>
</div>
)}
</motion.div>
))}
</div>
{/* Labels */}
<div className="space-y-6">
{market.map((m, idx) => (
<motion.div
key={m.id}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.5 + idx * 0.15 }}
className="flex items-center gap-4"
>
<div className={`w-3 h-3 rounded-full ${textColors[idx]} bg-current`} />
<div>
<div className="flex items-center gap-2">
<span className={`text-sm font-bold ${textColors[idx]}`}>{segments[idx]}</span>
<span className="text-xs text-white/30">{labels[idx]}</span>
</div>
<div className="text-2xl font-bold text-white">
<AnimatedCounter
target={m.value_eur / 1_000_000_000}
suffix={lang === 'de' ? ' Mrd. EUR' : 'B EUR'}
decimals={1}
duration={1500}
/>
</div>
<div className="text-xs text-white/40">
{i.market.growth}: {m.growth_rate_pct}% · {i.market.source}: {m.source}
</div>
</div>
</motion.div>
))}
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,54 @@
'use client'
import { motion } from 'framer-motion'
import { Language } from '@/lib/types'
import { t } from '@/lib/i18n'
import { AlertTriangle, Scale, Shield } from 'lucide-react'
import GlassCard from '../ui/GlassCard'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
interface ProblemSlideProps {
lang: Language
}
const icons = [AlertTriangle, Scale, Shield]
export default function ProblemSlide({ lang }: ProblemSlideProps) {
const i = t(lang)
return (
<div>
<FadeInView className="text-center mb-12">
<h2 className="text-4xl md:text-5xl font-bold mb-3">
<GradientText>{i.problem.title}</GradientText>
</h2>
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.problem.subtitle}</p>
</FadeInView>
<div className="grid md:grid-cols-3 gap-6 mb-12">
{i.problem.cards.map((card, idx) => {
const Icon = icons[idx]
return (
<GlassCard key={idx} delay={0.2 + idx * 0.15} className="text-center">
<div className="w-12 h-12 mx-auto mb-4 rounded-xl bg-red-500/10 flex items-center justify-center">
<Icon className="w-6 h-6 text-red-400" />
</div>
<h3 className="text-lg font-bold mb-2 text-white">{card.title}</h3>
<p className="text-3xl font-bold text-red-400 mb-3">{card.stat}</p>
<p className="text-sm text-white/50 leading-relaxed">{card.desc}</p>
</GlassCard>
)
})}
</div>
<FadeInView delay={0.8} className="max-w-3xl mx-auto">
<blockquote className="text-center">
<p className="text-lg md:text-xl text-white/70 italic leading-relaxed">
&ldquo;{i.problem.quote}&rdquo;
</p>
</blockquote>
</FadeInView>
</div>
)
}

View File

@@ -0,0 +1,33 @@
'use client'
import { Language, PitchProduct } from '@/lib/types'
import { t } from '@/lib/i18n'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import PricingCard from '../ui/PricingCard'
interface ProductSlideProps {
lang: Language
products: PitchProduct[]
}
export default function ProductSlide({ lang, products }: ProductSlideProps) {
const i = t(lang)
return (
<div>
<FadeInView className="text-center mb-12">
<h2 className="text-4xl md:text-5xl font-bold mb-3">
<GradientText>{i.product.title}</GradientText>
</h2>
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.product.subtitle}</p>
</FadeInView>
<div className="grid md:grid-cols-3 gap-6">
{products.map((product, idx) => (
<PricingCard key={product.id} product={product} lang={lang} delay={0.2 + idx * 0.15} />
))}
</div>
</div>
)
}

View File

@@ -0,0 +1,57 @@
'use client'
import { motion } from 'framer-motion'
import { Language } from '@/lib/types'
import { t } from '@/lib/i18n'
import { Server, ShieldCheck, Bot } from 'lucide-react'
import GlassCard from '../ui/GlassCard'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import BrandName from '../ui/BrandName'
interface SolutionSlideProps {
lang: Language
}
const icons = [Server, ShieldCheck, Bot]
const colors = ['from-blue-500 to-cyan-500', 'from-indigo-500 to-purple-500', 'from-purple-500 to-pink-500']
export default function SolutionSlide({ lang }: SolutionSlideProps) {
const i = t(lang)
return (
<div>
<FadeInView className="text-center mb-12">
<h2 className="text-4xl md:text-5xl font-bold mb-3">
<GradientText>{i.solution.title}</GradientText>
</h2>
<p className="text-lg text-white/50 max-w-2xl mx-auto">
<BrandName /> {lang === 'de' ? 'Compliance auf Autopilot' : 'Compliance on Autopilot'}
</p>
</FadeInView>
<div className="grid md:grid-cols-3 gap-6">
{i.solution.pillars.map((pillar, idx) => {
const Icon = icons[idx]
return (
<motion.div
key={idx}
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.3 + idx * 0.2, duration: 0.5 }}
>
<GlassCard className="text-center h-full" delay={0}>
<div className={`w-16 h-16 mx-auto mb-5 rounded-2xl bg-gradient-to-br ${colors[idx]}
flex items-center justify-center shadow-lg`}>
<Icon className="w-8 h-8 text-white" />
</div>
<h3 className="text-xl font-bold mb-3 text-white">{pillar.title}</h3>
<p className="text-sm text-white/50 leading-relaxed">{pillar.desc}</p>
</GlassCard>
</motion.div>
)
})}
</div>
</div>
)
}

View File

@@ -0,0 +1,76 @@
'use client'
import { motion } from 'framer-motion'
import { Language, PitchTeamMember } from '@/lib/types'
import { t } from '@/lib/i18n'
import { User } from 'lucide-react'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
interface TeamSlideProps {
lang: Language
team: PitchTeamMember[]
}
export default function TeamSlide({ lang, team }: TeamSlideProps) {
const i = t(lang)
return (
<div>
<FadeInView className="text-center mb-12">
<h2 className="text-4xl md:text-5xl font-bold mb-3">
<GradientText>{i.team.title}</GradientText>
</h2>
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.team.subtitle}</p>
</FadeInView>
<div className="grid md:grid-cols-2 gap-8 max-w-4xl mx-auto">
{team.map((member, idx) => (
<motion.div
key={member.id}
initial={{ opacity: 0, x: idx === 0 ? -40 : 40 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.3 + idx * 0.2, duration: 0.6 }}
className="bg-white/[0.08] backdrop-blur-xl border border-white/10 rounded-3xl p-8"
>
<div className="flex items-start gap-5">
{/* Avatar */}
<div className="w-20 h-20 rounded-2xl bg-gradient-to-br from-indigo-500 to-purple-600
flex items-center justify-center shrink-0 shadow-lg">
<User className="w-10 h-10 text-white" />
</div>
<div className="flex-1">
<h3 className="text-xl font-bold text-white mb-1">{member.name}</h3>
<p className="text-indigo-400 text-sm font-medium mb-3">
{lang === 'de' ? member.role_de : member.role_en}
</p>
<p className="text-sm text-white/50 leading-relaxed mb-4">
{lang === 'de' ? member.bio_de : member.bio_en}
</p>
{/* Equity */}
<div className="flex items-center gap-2 mb-3">
<span className="text-xs text-white/40">{i.team.equity}:</span>
<span className="text-sm font-bold text-white">{member.equity_pct}%</span>
</div>
{/* Expertise Tags */}
<div className="flex flex-wrap gap-1.5">
{(member.expertise || []).map((skill, sidx) => (
<span
key={sidx}
className="text-xs px-2.5 py-1 rounded-full bg-indigo-500/10 text-indigo-300 border border-indigo-500/20"
>
{skill}
</span>
))}
</div>
</div>
</div>
</motion.div>
))}
</div>
</div>
)
}

View File

@@ -0,0 +1,127 @@
'use client'
import { motion } from 'framer-motion'
import { Language, PitchFunding } from '@/lib/types'
import { t } from '@/lib/i18n'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import AnimatedCounter from '../ui/AnimatedCounter'
import GlassCard from '../ui/GlassCard'
import { Target, Calendar, FileText } from 'lucide-react'
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts'
interface TheAskSlideProps {
lang: Language
funding: PitchFunding
}
const COLORS = ['#6366f1', '#a78bfa', '#60a5fa', '#34d399', '#fbbf24']
export default function TheAskSlide({ lang, funding }: TheAskSlideProps) {
const i = t(lang)
const useOfFunds = funding?.use_of_funds || []
const pieData = useOfFunds.map((item) => ({
name: lang === 'de' ? item.label_de : item.label_en,
value: item.percentage,
}))
return (
<div>
<FadeInView className="text-center mb-10">
<h2 className="text-4xl md:text-5xl font-bold mb-3">
<GradientText>{i.theAsk.title}</GradientText>
</h2>
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.theAsk.subtitle}</p>
</FadeInView>
{/* Main Number */}
<FadeInView delay={0.2} className="text-center mb-10">
<motion.div
initial={{ scale: 0.5, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ delay: 0.4, type: 'spring', stiffness: 200 }}
>
<p className="text-6xl md:text-8xl font-bold text-white mb-2">
<AnimatedCounter target={200} suffix="k" duration={2000} />
<span className="text-3xl md:text-4xl text-white/50 ml-2">EUR</span>
</p>
</motion.div>
</FadeInView>
{/* Details */}
<div className="grid md:grid-cols-3 gap-4 mb-8">
<GlassCard delay={0.5} className="text-center p-5">
<FileText className="w-6 h-6 text-indigo-400 mx-auto mb-2" />
<p className="text-xs text-white/40 mb-1">{i.theAsk.instrument}</p>
<p className="text-lg font-bold text-white">{funding?.instrument || 'SAFE'}</p>
</GlassCard>
<GlassCard delay={0.6} className="text-center p-5">
<Calendar className="w-6 h-6 text-purple-400 mx-auto mb-2" />
<p className="text-xs text-white/40 mb-1">{i.theAsk.targetDate}</p>
<p className="text-lg font-bold text-white">Q3 2026</p>
</GlassCard>
<GlassCard delay={0.7} className="text-center p-5">
<Target className="w-6 h-6 text-blue-400 mx-auto mb-2" />
<p className="text-xs text-white/40 mb-1">{lang === 'de' ? 'Runway' : 'Runway'}</p>
<p className="text-lg font-bold text-white">18 {lang === 'de' ? 'Monate' : 'Months'}</p>
</GlassCard>
</div>
{/* Use of Funds */}
<FadeInView delay={0.8}>
<GlassCard hover={false} className="p-6">
<h3 className="text-lg font-semibold text-white mb-4 text-center">{i.theAsk.useOfFunds}</h3>
<div className="flex flex-col md:flex-row items-center gap-8">
{/* Pie Chart */}
<div className="w-48 h-48">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={pieData}
cx="50%"
cy="50%"
innerRadius={50}
outerRadius={80}
dataKey="value"
stroke="none"
>
{pieData.map((_, idx) => (
<Cell key={idx} fill={COLORS[idx % COLORS.length]} />
))}
</Pie>
<Tooltip
contentStyle={{
background: 'rgba(10, 10, 26, 0.9)',
border: '1px solid rgba(255,255,255,0.1)',
borderRadius: 8,
color: '#fff',
fontSize: 13,
}}
formatter={(value: number) => `${value}%`}
/>
</PieChart>
</ResponsiveContainer>
</div>
{/* Legend */}
<div className="flex-1 space-y-3">
{useOfFunds.map((item, idx) => (
<div key={idx} className="flex items-center gap-3">
<div className="w-3 h-3 rounded-full" style={{ backgroundColor: COLORS[idx] }} />
<span className="flex-1 text-sm text-white/70">
{lang === 'de' ? item.label_de : item.label_en}
</span>
<span className="text-sm font-bold text-white">{item.percentage}%</span>
<span className="text-xs text-white/30">
{((funding.amount_eur * item.percentage) / 100).toLocaleString('de-DE')} EUR
</span>
</div>
))}
</div>
</div>
</GlassCard>
</FadeInView>
</div>
)
}

View File

@@ -0,0 +1,58 @@
'use client'
import { Language, PitchMilestone, PitchMetric } from '@/lib/types'
import { t } from '@/lib/i18n'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import GlassCard from '../ui/GlassCard'
import Timeline from '../ui/Timeline'
import LiveIndicator from '../ui/LiveIndicator'
interface TractionSlideProps {
lang: Language
milestones: PitchMilestone[]
metrics: PitchMetric[]
}
export default function TractionSlide({ lang, milestones, metrics }: TractionSlideProps) {
const i = t(lang)
return (
<div>
<FadeInView className="text-center mb-10">
<h2 className="text-4xl md:text-5xl font-bold mb-3">
<GradientText>{i.traction.title}</GradientText>
</h2>
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.traction.subtitle}</p>
</FadeInView>
<div className="grid md:grid-cols-2 gap-8">
{/* KPI Cards */}
<div>
<div className="grid grid-cols-2 gap-3 mb-6">
{metrics.slice(0, 6).map((m, idx) => (
<GlassCard key={m.id} delay={0.2 + idx * 0.08} className="p-4">
<div className="flex items-center justify-between mb-1">
<span className="text-xs text-white/40">
{lang === 'de' ? m.label_de : m.label_en}
</span>
{m.is_live && <LiveIndicator />}
</div>
<p className="text-2xl font-bold text-white">
{m.value}{m.unit ? ` ${m.unit}` : ''}
</p>
</GlassCard>
))}
</div>
</div>
{/* Timeline */}
<FadeInView delay={0.4}>
<div className="bg-white/[0.03] rounded-2xl p-5 border border-white/5 max-h-[400px] overflow-y-auto">
<Timeline milestones={milestones} lang={lang} />
</div>
</FadeInView>
</div>
</div>
)
}